diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index eb460e738..4bc73fdcf 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: crates: - - stm32 + #- stm32 # runs out of disk space... - rest # This will ensure at most one doc build job is running at a time @@ -46,7 +46,7 @@ jobs: - name: Install docserver run: | - wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.3/builder" + wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder" chmod +x /usr/local/bin/builder - name: build-stm32 @@ -69,12 +69,15 @@ jobs: builder ./embassy-futures crates/embassy-futures/git.zup builder ./embassy-lora crates/embassy-lora/git.zup builder ./embassy-net crates/embassy-net/git.zup + builder ./embassy-net-driver crates/embassy-net-driver/git.zup + builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup builder ./embassy-nrf crates/embassy-nrf/git.zup builder ./embassy-rp crates/embassy-rp/git.zup builder ./embassy-sync crates/embassy-sync/git.zup builder ./embassy-time crates/embassy-time/git.zup builder ./embassy-usb crates/embassy-usb/git.zup builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup + builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup - name: upload run: | diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b93c8783d..3bfe5ef03 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -68,5 +68,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Test - run: cd embassy-sync && cargo test + + - name: Test boot + working-directory: ./embassy-boot/boot + run: cargo test && cargo test --features "ed25519-dalek" && cargo test --features "ed25519-salty" + + - name: Test sync + working-directory: ./embassy-sync + run: cargo test diff --git a/.vscode/settings.json b/.vscode/settings.json index 5e9e51799..db37b64ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,29 +1,27 @@ { "editor.formatOnSave": true, - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.checkOnSave.noDefaultFeatures": true, + "rust-analyzer.check.allTargets": false, + "rust-analyzer.check.noDefaultFeatures": true, "rust-analyzer.cargo.noDefaultFeatures": true, "rust-analyzer.procMacro.enable": true, "rust-analyzer.cargo.target": "thumbv7em-none-eabi", + //"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", "rust-analyzer.cargo.features": [ - // These are needed to prevent embassy-net from failing to build - //"embassy-net/medium-ethernet", - //"embassy-net/tcp", - //"embassy-net/pool-16", - //"time-tick-16mhz", - //"defmt-timestamp-uptime", "nightly", - //"unstable-traits", ], "rust-analyzer.linkedProjects": [ // Declare for the target you wish to develop - //"embassy-executor/Cargo.toml", - //"embassy-sync/Cargo.toml", - "examples/nrf/Cargo.toml", + // "embassy-executor/Cargo.toml", + // "embassy-sync/Cargo.toml", + "examples/nrf52840/Cargo.toml", + //"examples/nrf5340/Cargo.toml", + // "examples/nrf-rtos-trace/Cargo.toml", // "examples/rp/Cargo.toml", // "examples/std/Cargo.toml", // "examples/stm32f0/Cargo.toml", // "examples/stm32f1/Cargo.toml", + // "examples/stm32f2/Cargo.toml", + // "examples/stm32f3/Cargo.toml", // "examples/stm32f4/Cargo.toml", // "examples/stm32f7/Cargo.toml", // "examples/stm32g0/Cargo.toml", @@ -32,8 +30,11 @@ // "examples/stm32l0/Cargo.toml", // "examples/stm32l1/Cargo.toml", // "examples/stm32l4/Cargo.toml", + // "examples/stm32l5/Cargo.toml", // "examples/stm32u5/Cargo.toml", + // "examples/stm32wb/Cargo.toml", // "examples/stm32wb55/Cargo.toml", + // "examples/stm32wl/Cargo.toml", // "examples/stm32wl55/Cargo.toml", // "examples/wasm/Cargo.toml", ], diff --git a/README.md b/README.md index 9f08bf676..938f2f4a6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Rust's async/await allows No more messing with hardware timers. embassy_time provides Instant, Duration and Timer types that are globally available and never overflow. - **Real-time ready** - -Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the example. +Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the example. - **Low-power ready** - Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting. @@ -31,7 +31,7 @@ The embassy-net network stac The nrf-softdevice crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. - **LoRa** - -embassy-lora supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX127x transceivers. +embassy-lora supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers. - **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. @@ -87,7 +87,8 @@ async fn main(spawner: Spawner) { Examples are found in the `examples/` folder seperated by the chip manufacturer they are designed to run on. For example: -* `examples/nrf` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards. +* `examples/nrf52840` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards. +* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095). * `examples/stm32xx` for the various STM32 families. * `examples/rp` are for the RP2040 chip. * `examples/std` are designed to run locally on your PC. @@ -110,7 +111,7 @@ cargo install probe-run - Change directory to the sample's base directory. For example: ```bash -cd examples/nrf +cd examples/nrf52840 ``` - Run the example diff --git a/ci.sh b/ci.sh index cd1c0786c..4199f91d3 100755 --- a/ci.sh +++ b/ci.sh @@ -36,10 +36,12 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16 \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,nightly \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits,nightly \ + --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ + --- 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,unstable-traits \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ @@ -61,7 +63,8 @@ cargo batch \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \ @@ -74,18 +77,21 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5ub,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5jb,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \ --- 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/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 docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \ - --- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf \ + --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \ + --- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \ --- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \ --- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \ --- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \ @@ -93,6 +99,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \ --- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \ --- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f7 \ + --- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \ --- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32g0 \ --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \ --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \ @@ -103,7 +110,9 @@ cargo batch \ --- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \ --- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \ - --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/nrf --bin b \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 --out-dir out/examples/boot/nrf --bin b \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns --out-dir out/examples/boot/nrf --bin b \ + --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/rp --bin b \ --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f3 --bin b \ --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f7 --bin b \ --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32h7 --bin b \ @@ -112,6 +121,8 @@ cargo batch \ --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32l4 --bin b \ --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wl --bin b \ --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ + --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ --- build --release --manifest-path examples/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/bluepill-stm32f103c8 \ diff --git a/ci_stable.sh b/ci_stable.sh index 0332c3faf..b4b0b83e7 100755 --- a/ci_stable.sh +++ b/ci_stable.sh @@ -13,8 +13,8 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16 \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits \ + --- 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,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \ @@ -36,7 +36,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55vy,defmt,exti,time-driver-any,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55uc-cm4,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55cc-cm4,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r9zi,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303vc,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any \ @@ -65,5 +65,5 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,unstable-traits \ - --- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf --bin raw_spawn \ + --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf52840 --bin raw_spawn \ --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --no-default-features --out-dir out/examples/stm32l0 --bin raw_spawn \ diff --git a/docs/modules/ROOT/examples/basic/Cargo.toml b/docs/modules/ROOT/examples/basic/Cargo.toml index c13f546e2..d9f8a285a 100644 --- a/docs/modules/ROOT/examples/basic/Cargo.toml +++ b/docs/modules/ROOT/examples/basic/Cargo.toml @@ -6,14 +6,13 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly"] } +embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] } embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] } defmt = "0.3" defmt-rtt = "0.3" -cortex-m = "0.7.3" +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" -embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/nrf/build.rs b/docs/modules/ROOT/examples/basic/build.rs similarity index 100% rename from examples/nrf/build.rs rename to docs/modules/ROOT/examples/basic/build.rs diff --git a/examples/nrf/memory.x b/docs/modules/ROOT/examples/basic/memory.x similarity index 100% rename from examples/nrf/memory.x rename to docs/modules/ROOT/examples/basic/memory.x diff --git a/docs/modules/ROOT/pages/basic_application.adoc b/docs/modules/ROOT/pages/basic_application.adoc index 4dc4a6359..3f4f16e28 100644 --- a/docs/modules/ROOT/pages/basic_application.adoc +++ b/docs/modules/ROOT/pages/basic_application.adoc @@ -21,7 +21,7 @@ Then, what follows are some declarations on how to deal with panics and faults. [source,rust] ---- -include::example$basic/src/main.rs[lines="11..12"] +include::example$basic/src/main.rs[lines="10"] ---- === Task declaration @@ -30,7 +30,7 @@ After a bit of import declaration, the tasks run by the application should be de [source,rust] ---- -include::example$basic/src/main.rs[lines="13..22"] +include::example$basic/src/main.rs[lines="12..20"] ---- An embassy task must be declared `async`, and may NOT take generic arguments. In this case, we are handed the LED that should be blinked and the interval of the blinking. @@ -45,23 +45,10 @@ The `Spawner` is the way the main application spawns other tasks. The `Periphera [source,rust] ---- -include::example$basic/src/main.rs[lines="23..-1"] +include::example$basic/src/main.rs[lines="22..-1"] ---- -`#[embassy_executor::main]` takes an optional `config` parameter specifying a function that returns an instance of HAL's `Config` struct. For example: - -```rust -fn embassy_config() -> embassy_nrf::config::Config { - embassy_nrf::config::Config::default() -} - -#[embassy_executor::main(config = "embassy_config()")] -async fn main(_spawner: Spawner, p: embassy_nrf::Peripherals) { - // ... -} -``` - -What happens when the `blinker` task have been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following: +What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following: . Creates an Embassy Executor . Initializes the microcontroller HAL to get the `Peripherals` @@ -76,7 +63,7 @@ The project definition needs to contain the embassy dependencies: [source,toml] ---- -include::example$basic/Cargo.toml[lines="8..9"] +include::example$basic/Cargo.toml[lines="9..11"] ---- Depending on your microcontroller, you may need to replace `embassy-nrf` with something else (`embassy-stm32` for STM32. Remember to update feature flags as well). diff --git a/docs/modules/ROOT/pages/bootloader.adoc b/docs/modules/ROOT/pages/bootloader.adoc index ae92e9d5d..b7215e52a 100644 --- a/docs/modules/ROOT/pages/bootloader.adoc +++ b/docs/modules/ROOT/pages/bootloader.adoc @@ -6,7 +6,7 @@ The bootloader can be used either as a library or be flashed directly if you are By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. -The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. +The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. The bootloader optionally supports the verification of firmware that has been digitally signed (recommended). == Hardware support @@ -15,6 +15,7 @@ The bootloader supports * nRF52 with and without softdevice * STM32 L4, WB, WL, L1, L0, F3, F7 and H7 +* Raspberry Pi: RP2040 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. @@ -25,12 +26,69 @@ image::bootloader_flash.png[Bootloader flash layout] The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts: -* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash. -* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader. -* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. -* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a flag is set to instruct the bootloader that the partitions should be swapped. +* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs. +* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader. The size required for this partition depends on the size of your application. +* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition, since the swap algorithm uses the extra space to ensure power safe copy of data: ++ +Partition Size~dfu~= Partition Size~active~+ Page Size~active~ ++ +All values are specified in bytes. + +* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a magic field is written to instruct the bootloader that the partitions should be swapped. This partition must be able to store a magic field as well as the partition swap progress. The partition size given by: ++ +Partition Size~state~ = Write Size~state~ + (2 × Partition Size~active~ / Page Size~active~) ++ +All values are specified in bytes. The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash. The page size used by the bootloader is determined by the lowest common multiple of the ACTIVE and DFU page sizes. The BOOTLOADER_STATE partition must be big enough to store one word per page in the ACTIVE and DFU partitions combined. The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice. + +=== FirmwareUpdater + +The `FirmwareUpdater` is an object for conveniently flashing firmware to the DFU partition and subsequently marking it as being ready for swapping with the active partition on the next reset. Its principle methods are `write_firmware`, which is called once per the size of the flash "write block" (typically 4KiB), and `mark_updated`, which is the final call. + +=== Verification + +The bootloader supports the verification of firmware that has been flashed to the DFU partition. Verification requires that firmware has been signed digitally using link:https://ed25519.cr.yp.to/[`ed25519`] signatures. With verification enabled, the `FirmwareUpdater::verify_and_mark_updated` method is called in place of `mark_updated`. A public key and signature are required, along with the actual length of the firmware that has been flashed. If verification fails then the firmware will not be marked as updated and therefore be rejected. + +Signatures are normally conveyed with the firmware to be updated and not written to flash. How signatures are provided is a firmware responsibility. + +To enable verification use either the `ed25519-dalek` or `ed25519-salty` features when depending on the `embassy-boot` crate. We recommend `ed25519-salty` at this time due to its small size. + +==== Tips on keys and signing with ed25519 + +Ed25519 is a public key signature system where you are responsible for keeping the private key secure. We recommend embedding the *public* key in your program so that it can be easily passed to `verify_and_mark_updated`. An example declaration of the public key in your firmware: + +[source, rust] +---- +static PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("key.pub"); +---- + +Signatures are often conveyed along with firmware by appending them. + +Ed25519 keys can be generated by a variety of tools. We recommend link:https://man.openbsd.org/signify[`signify`] as it is in wide use to sign and verify OpenBSD distributions, and is straightforward to use. + +The following set of Bash commands can be used to generate public and private keys on Unix platforms, and also generate a local `key.pub` file with the `signify` file headers removed. Declare a `SECRETS_DIR` environment variable in a secure location. + +[source, bash] +---- +signify -G -n -p $SECRETS_DIR/key.pub -s $SECRETS_DIR/key.sec +tail -n1 $SECRETS_DIR/key.pub | base64 -d -i - | dd ibs=10 skip=1 > key.pub +chmod 700 $SECRETS_DIR/key.sec +export SECRET_SIGNING_KEY=$(tail -n1 $SECRETS_DIR/key.sec) +---- + +Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware filename of `myfirmware`: + +[source, bash] +---- +shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt +cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt +signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig +cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed +tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed +---- + +Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/getting_started.adoc b/docs/modules/ROOT/pages/getting_started.adoc index f3492a3d0..9015d7845 100644 --- a/docs/modules/ROOT/pages/getting_started.adoc +++ b/docs/modules/ROOT/pages/getting_started.adoc @@ -45,7 +45,7 @@ You can run an example by opening a terminal and entering the following commands [source, bash] ---- -cd examples/nrf +cd examples/nrf52840 cargo run --bin blinky --release ---- diff --git a/docs/modules/ROOT/pages/layer_by_layer.adoc b/docs/modules/ROOT/pages/layer_by_layer.adoc index a96dd9fe2..a78a64a97 100644 --- a/docs/modules/ROOT/pages/layer_by_layer.adoc +++ b/docs/modules/ROOT/pages/layer_by_layer.adoc @@ -8,7 +8,7 @@ The application we'll write is a simple 'push button, blink led' application, wh == PAC version -The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provide distinct types +The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code. Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use. @@ -20,13 +20,13 @@ The blinky app using PAC is shown below: include::example$layer-by-layer/blinky-pac/src/main.rs[] ---- -As you can see, there are a lot of code needed to enable the peripheral clocks, configuring the input pins and the output pins of the application. +As you can see, a lot of code is needed to enable the peripheral clocks and to configure the input pins and the output pins of the application. Another downside of this application is that it is busy-looping while polling the button state. This prevents the microcontroller from utilizing any sleep mode to save power. == HAL version -To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such +To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such as: * Automatically enabling the peripheral clock when you're using the peripheral * Deriving and applying register configuration from higher level types @@ -39,7 +39,7 @@ The HAL example is shown below: include::example$layer-by-layer/blinky-hal/src/main.rs[] ---- -As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` hides all the details accessing the GPIO registers, and allow you to use a much simpler API to query the state of the button and toggle the LED output accordingly. +As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` types hide all the details of accessing the GPIO registers and allow you to use a much simpler API for querying the state of the button and toggling the LED output. The same downside from the PAC example still applies though: the application is busy looping and consuming more power than necessary. diff --git a/docs/modules/ROOT/pages/stm32.adoc b/docs/modules/ROOT/pages/stm32.adoc index 8ed9ab04b..7bfc0592b 100644 --- a/docs/modules/ROOT/pages/stm32.adoc +++ b/docs/modules/ROOT/pages/stm32.adoc @@ -4,9 +4,9 @@ The link:https://github.com/embassy-rs/embassy/tree/master/embassy-stm32[Embassy == The infinite variant problem -STM32 microcontrollers comes in many families and flavors, and supporting all of them is a big undertaking. Embassy has taken advantage of the fact +STM32 microcontrollers come in many families, and flavors and supporting all of them is a big undertaking. Embassy has taken advantage of the fact that the STM32 peripheral versions are shared across chip families. Instead of re-implementing the SPI -peripheral for every STM32 chip family, embassy have a single SPI implementation that depends on +peripheral for every STM32 chip family, embassy has a single SPI implementation that depends on code-generated register types that are identical for STM32 families with the same version of a given peripheral. === The metapac diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index 54c67a375..0b0c77b1e 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml @@ -1,26 +1,50 @@ [package] edition = "2021" name = "embassy-boot" -version = "0.1.0" -description = "Bootloader using Embassy" +version = "0.1.1" +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" +categories = [ + "embedded", + "no-std", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] [lib] [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4", optional = true } +ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } embedded-storage = "0.3.0" embedded-storage-async = "0.3.0" +salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } +signature = { version = "1.6.4", default-features = false } [dev-dependencies] log = "0.4" env_logger = "0.9" -rand = "0.8" +rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version futures = { version = "0.3", features = ["executor"] } + +[dev-dependencies.ed25519-dalek] +default_features = false +features = ["rand", "std", "u32_backend"] + +[features] +ed25519-dalek = ["dep:ed25519-dalek", "_verify"] +ed25519-salty = ["dep:salty", "_verify"] + +#Internal features +_verify = [] \ No newline at end of file diff --git a/embassy-boot/README.md b/embassy-boot/boot/README.md similarity index 100% rename from embassy-boot/README.md rename to embassy-boot/boot/README.md diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 8286601ec..be254e9d7 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -1,7 +1,7 @@ #![feature(type_alias_impl_trait)] #![no_std] #![warn(missing_docs)] -#![doc = include_str!("../../README.md")] +#![doc = include_str!("../README.md")] mod fmt; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; @@ -52,6 +52,16 @@ pub enum BootError { BadMagic, } +#[cfg(feature = "defmt")] +impl defmt::Format for BootError { + fn format(&self, fmt: defmt::Formatter) { + match self { + BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), + BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), + } + } +} + impl From for BootError where E: NorFlashError, @@ -150,7 +160,7 @@ impl BootLoader { /// +-----------+------------+--------+--------+--------+--------+ /// | Active | 0 | 1 | 2 | 3 | - | /// | DFU | 0 | 3 | 2 | 1 | X | - /// +-----------+-------+--------+--------+--------+--------+ + /// +-----------+------------+--------+--------+--------+--------+ /// /// The algorithm starts by copying 'backwards', and after the first step, the layout is /// as follows: @@ -557,6 +567,33 @@ where self.state } } +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + FirmwareUpdaterError::Flash(error.kind()) + } +} /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to /// 'mess up' the internal bootloader state @@ -609,7 +646,11 @@ impl FirmwareUpdater { /// This is useful to check if the bootloader has just done a swap, in order /// to do verifications and self-tests of the new image before calling /// `mark_booted`. - pub async fn get_state(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result { + pub async fn get_state( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result { flash.read(self.state.from as u32, aligned).await?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { @@ -619,12 +660,126 @@ impl FirmwareUpdater { } } + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( + &mut self, + _flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: usize, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let _end = self.dfu.from + _update_len; + let _read_size = _aligned.len(); + + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_end <= self.dfu.to); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned).await?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned).await?; + digest.update(&_aligned[0..remaining]); + } + + public_key + .verify(&digest.finalize(), &signature) + .map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Sha512, Signature}; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned).await?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned).await?; + digest.update(&_aligned[0..remaining]); + } + + let message = digest.finalize(); + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic(_aligned, SWAP_MAGIC, _flash).await + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub async fn mark_updated(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic(aligned, SWAP_MAGIC, flash).await } @@ -634,7 +789,11 @@ impl FirmwareUpdater { /// # Safety /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub async fn mark_booted(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + pub async fn mark_booted( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic(aligned, BOOT_MAGIC, flash).await } @@ -644,7 +803,7 @@ impl FirmwareUpdater { aligned: &mut [u8], magic: u8, flash: &mut F, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { flash.read(self.state.from as u32, aligned).await?; if aligned.iter().any(|&b| b != magic) { @@ -672,7 +831,7 @@ impl FirmwareUpdater { data: &[u8], flash: &mut F, block_size: usize, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); flash @@ -700,7 +859,10 @@ impl FirmwareUpdater { /// /// Using this instead of `write_firmware` allows for an optimized API in /// exchange for added complexity. - pub async fn prepare_update(&mut self, flash: &mut F) -> Result { + pub async fn prepare_update( + &mut self, + flash: &mut F, + ) -> Result { flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?; trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); @@ -717,7 +879,11 @@ impl FirmwareUpdater { /// This is useful to check if the bootloader has just done a swap, in order /// to do verifications and self-tests of the new image before calling /// `mark_booted`. - pub fn get_state_blocking(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result { + pub fn get_state_blocking( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result { flash.read(self.state.from as u32, aligned)?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { @@ -727,12 +893,126 @@ impl FirmwareUpdater { } } + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(feature = "_verify")] + pub fn verify_and_mark_updated_blocking( + &mut self, + _flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: usize, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let _end = self.dfu.from + _update_len; + let _read_size = _aligned.len(); + + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_end <= self.dfu.to); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned)?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned)?; + digest.update(&_aligned[0..remaining]); + } + + public_key + .verify(&digest.finalize(), &signature) + .map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Sha512, Signature}; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned)?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned)?; + digest.update(&_aligned[0..remaining]); + } + + let message = digest.finalize(); + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash) + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub fn mark_updated_blocking(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + #[cfg(not(feature = "_verify"))] + pub fn mark_updated_blocking( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic_blocking(aligned, SWAP_MAGIC, flash) } @@ -742,7 +1022,11 @@ impl FirmwareUpdater { /// # Safety /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub fn mark_booted_blocking(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + pub fn mark_booted_blocking( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic_blocking(aligned, BOOT_MAGIC, flash) } @@ -752,7 +1036,7 @@ impl FirmwareUpdater { aligned: &mut [u8], magic: u8, flash: &mut F, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { flash.read(self.state.from as u32, aligned)?; if aligned.iter().any(|&b| b != magic) { @@ -780,7 +1064,7 @@ impl FirmwareUpdater { data: &[u8], flash: &mut F, block_size: usize, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); flash.erase( @@ -804,7 +1088,10 @@ impl FirmwareUpdater { /// /// Using this instead of `write_firmware_blocking` allows for an optimized /// API in exchange for added complexity. - pub fn prepare_update_blocking(&mut self, flash: &mut F) -> Result { + pub fn prepare_update_blocking( + &mut self, + flash: &mut F, + ) -> Result { flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); @@ -953,6 +1240,7 @@ mod tests { } #[test] + #[cfg(not(feature = "_verify"))] fn test_swap_state() { const STATE: Partition = Partition::new(0, 4096); const ACTIVE: Partition = Partition::new(4096, 61440); @@ -1022,6 +1310,7 @@ mod tests { } #[test] + #[cfg(not(feature = "_verify"))] fn test_separate_flash_active_page_biggest() { const STATE: Partition = Partition::new(2048, 4096); const ACTIVE: Partition = Partition::new(4096, 16384); @@ -1074,6 +1363,7 @@ mod tests { } #[test] + #[cfg(not(feature = "_verify"))] fn test_separate_flash_dfu_page_biggest() { const STATE: Partition = Partition::new(2048, 4096); const ACTIVE: Partition = Partition::new(4096, 16384); @@ -1133,6 +1423,55 @@ mod tests { assert_partitions(ACTIVE, DFU, STATE, 4096, 4); } + #[test] + #[cfg(feature = "_verify")] + fn test_verify() { + // The following key setup is based on: + // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example + + use ed25519_dalek::Keypair; + use rand::rngs::OsRng; + + let mut csprng = OsRng {}; + let keypair: Keypair = Keypair::generate(&mut csprng); + + use ed25519_dalek::{Digest, Sha512, Signature, Signer}; + let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; + let mut digest = Sha512::new(); + digest.update(&firmware); + let message = digest.finalize(); + let signature: Signature = keypair.sign(&message); + + use ed25519_dalek::PublicKey; + let public_key: PublicKey = keypair.public; + + // Setup flash + + const STATE: Partition = Partition::new(0, 4096); + const DFU: Partition = Partition::new(4096, 8192); + let mut flash = MemFlash::<8192, 4096, 4>([0xff; 8192]); + + let firmware_len = firmware.len(); + + let mut write_buf = [0; 4096]; + write_buf[0..firmware_len].copy_from_slice(firmware); + NorFlash::write(&mut flash, DFU.from as u32, &write_buf).unwrap(); + + // On with the test + + let mut updater = FirmwareUpdater::new(DFU, STATE); + + let mut aligned = [0; 4]; + + assert!(block_on(updater.verify_and_mark_updated( + &mut flash, + &public_key.to_bytes(), + &signature.to_bytes(), + firmware_len, + &mut aligned, + )) + .is_ok()); + } struct MemFlash([u8; SIZE]); impl NorFlash @@ -1171,7 +1510,7 @@ mod tests { impl ReadNorFlash for MemFlash { - const READ_SIZE: usize = 4; + const READ_SIZE: usize = 1; fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { let len = buf.len(); @@ -1194,7 +1533,7 @@ mod tests { impl AsyncReadNorFlash for MemFlash { - const READ_SIZE: usize = 4; + const READ_SIZE: usize = 1; type ReadFuture<'a> = impl Future> + 'a; fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md new file mode 100644 index 000000000..02f35c0a6 --- /dev/null +++ b/embassy-boot/nrf/README.md @@ -0,0 +1,26 @@ +# embassy-boot-nrf + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for nRF. + +## Features + +* Load applications with our without the softdevice. +* Configure bootloader partitions based on linker script. +* Using watchdog timer to detect application failure. + + +## Minimum supported Rust version (MSRV) + +`embassy-boot-nrf` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index 385e089fe..f40ae62d6 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] #![feature(type_alias_impl_trait)] #![warn(missing_docs)] -#![doc = include_str!("../../README.md")] +#![doc = include_str!("../README.md")] mod fmt; pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig}; @@ -17,9 +17,9 @@ pub struct BootLoader { page: AlignedBuffer, } -impl BootLoader { +impl Default for BootLoader { /// Create a new bootloader instance using parameters from linker script - pub fn default() -> Self { + fn default() -> Self { extern "C" { static __bootloader_state_start: u32; static __bootloader_state_end: u32; @@ -54,7 +54,9 @@ impl BootLoader { Self::new(active, dfu, state) } +} +impl BootLoader { /// Create a new bootloader instance using the supplied partitions for active, dfu and state. pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { Self { @@ -147,11 +149,7 @@ pub struct WatchdogFlash<'d> { impl<'d> WatchdogFlash<'d> { /// Start a new watchdog with a given flash and WDT peripheral and a timeout - pub fn start(flash: Nvmc<'d>, wdt: WDT, timeout: u32) -> Self { - let mut config = wdt::Config::default(); - config.timeout_ticks = 32768 * timeout; // timeout seconds - config.run_during_sleep = true; - config.run_during_debug_halt = false; + pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self { let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { Ok(x) => x, Err(_) => { diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml new file mode 100644 index 000000000..ffc36a4e0 --- /dev/null +++ b/embassy-boot/rp/Cargo.toml @@ -0,0 +1,73 @@ +[package] +edition = "2021" +name = "embassy-boot-rp" +version = "0.1.0" +description = "Bootloader lib for RP2040 chips" +license = "MIT OR Apache-2.0" + +[package.metadata.embassy_docs] +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" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { path = "../../embassy-sync" } +embassy-rp = { path = "../../embassy-rp", default-features = false, features = ["nightly"] } +embassy-boot = { path = "../boot", default-features = false } +embassy-time = { path = "../../embassy-time", features = ["nightly"] } + +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.0" +embedded-storage-async = "0.3.0" +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-rp/defmt", +] +log = [ + "dep:log", + "embassy-boot/log", + "embassy-rp/log", +] +debug = ["defmt-rtt"] + +[profile.dev] +debug = 2 +debug-assertions = true +incremental = false +opt-level = 'z' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md new file mode 100644 index 000000000..c0c2d85fa --- /dev/null +++ b/embassy-boot/rp/README.md @@ -0,0 +1,26 @@ +# embassy-boot-rp + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for RP2040. + +NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. + +## Features + +* Configure bootloader partitions based on linker script. +* Load applications from active partition. + +## Minimum supported Rust version (MSRV) + +`embassy-boot-rp` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-boot/rp/build.rs b/embassy-boot/rp/build.rs new file mode 100644 index 000000000..2cbc7ef5e --- /dev/null +++ b/embassy-boot/rp/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } +} diff --git a/embassy-boot/rp/src/fmt.rs b/embassy-boot/rp/src/fmt.rs new file mode 100644 index 000000000..066970813 --- /dev/null +++ b/embassy-boot/rp/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +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 ),*); + } + }; +} + +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 ),*); + } + }; +} + +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 ),*); + } + }; +} + +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 ),*); + } + }; +} + +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")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +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 + } +} diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs new file mode 100644 index 000000000..6df34133e --- /dev/null +++ b/embassy-boot/rp/src/lib.rs @@ -0,0 +1,139 @@ +#![no_std] +#![feature(type_alias_impl_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; +use embassy_rp::flash::{Flash, ERASE_SIZE, WRITE_SIZE}; +use embassy_rp::peripherals::{FLASH, WATCHDOG}; +use embassy_rp::watchdog::Watchdog; +use embassy_time::Duration; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +/// A bootloader for RP2040 devices. +pub struct BootLoader { + boot: embassy_boot::BootLoader, + magic: AlignedBuffer, + page: AlignedBuffer, +} + +impl BootLoader { + /// Create a new bootloader instance using the supplied partitions for active, dfu and state. + pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + Self { + boot: embassy_boot::BootLoader::new(active, dfu, state), + magic: AlignedBuffer([0; WRITE_SIZE]), + page: AlignedBuffer([0; ERASE_SIZE]), + } + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping + /// firmware. + pub fn prepare(&mut self, flash: &mut F) -> usize { + match self.boot.prepare_boot(flash, self.magic.as_mut(), self.page.as_mut()) { + Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(), + Err(_) => panic!("boot prepare error!"), + } + } + + /// Boots the application. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + pub unsafe fn load(&mut self, start: usize) -> ! { + trace!("Loading app at 0x{:x}", start); + #[allow(unused_mut)] + let mut p = cortex_m::Peripherals::steal(); + #[cfg(not(armv6m))] + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start as u32); + + cortex_m::asm::bootload(start as *const u32) + } +} + +impl Default for BootLoader { + /// Create a new bootloader instance using parameters from linker script + fn default() -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_active_start: u32; + static __bootloader_active_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let active = unsafe { + Partition::new( + &__bootloader_active_start as *const u32 as usize, + &__bootloader_active_end as *const u32 as usize, + ) + }; + let dfu = unsafe { + Partition::new( + &__bootloader_dfu_start as *const u32 as usize, + &__bootloader_dfu_end as *const u32 as usize, + ) + }; + let state = unsafe { + Partition::new( + &__bootloader_state_start as *const u32 as usize, + &__bootloader_state_end as *const u32 as usize, + ) + }; + + trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); + trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); + trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); + + Self::new(active, dfu, state) + } +} + +/// A flash implementation that will feed a watchdog when touching flash. +pub struct WatchdogFlash<'d, const SIZE: usize> { + flash: Flash<'d, FLASH, SIZE>, + watchdog: Watchdog, +} + +impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { + /// Start a new watchdog with a given flash and watchdog peripheral and a timeout + pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { + let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash); + let mut watchdog = Watchdog::new(watchdog); + watchdog.start(timeout); + Self { flash, watchdog } + } +} + +impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { + type Error = as ErrorType>::Error; +} + +impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { + const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE; + const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.write(offset, data) + } +} + +impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { + const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE; + fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml index 9d12c6cfd..2fc169b32 100644 --- a/embassy-boot/stm32/Cargo.toml +++ b/embassy-boot/stm32/Cargo.toml @@ -15,7 +15,7 @@ target = "thumbv7em-none-eabi" [dependencies] defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } log = { version = "0.4", optional = true } embassy-sync = { path = "../../embassy-sync" } diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md index a82b730b9..cb134b534 100644 --- a/embassy-boot/stm32/README.md +++ b/embassy-boot/stm32/README.md @@ -1,11 +1,24 @@ -# Bootloader for STM32 +# embassy-boot-stm32 -The bootloader uses `embassy-boot` to interact with the flash. +An [Embassy](https://embassy.dev) project. -# Usage +An adaptation of `embassy-boot` for STM32. -Flash the bootloader +## Features -``` -cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx -``` +* Configure bootloader partitions based on linker script. +* Load applications from active partition. + +## Minimum supported Rust version (MSRV) + +`embassy-boot-stm32` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index edba39cca..82f712c4d 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] #![feature(type_alias_impl_trait)] #![warn(missing_docs)] -#![doc = include_str!("../../README.md")] +#![doc = include_str!("../README.md")] mod fmt; pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; @@ -14,43 +14,6 @@ pub struct BootLoader { } impl BootLoader { - /// Create a new bootloader instance using parameters from linker script - pub fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - Partition::new( - &__bootloader_active_start as *const u32 as usize, - &__bootloader_active_end as *const u32 as usize, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as usize, - &__bootloader_dfu_end as *const u32 as usize, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as usize, - &__bootloader_state_end as *const u32 as usize, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } - /// Create a new bootloader instance using the supplied partitions for active, dfu and state. pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { Self { @@ -85,3 +48,42 @@ impl BootLoader Default for BootLoader { + /// Create a new bootloader instance using parameters from linker script + fn default() -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_active_start: u32; + static __bootloader_active_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let active = unsafe { + Partition::new( + &__bootloader_active_start as *const u32 as usize, + &__bootloader_active_end as *const u32 as usize, + ) + }; + let dfu = unsafe { + Partition::new( + &__bootloader_dfu_start as *const u32 as usize, + &__bootloader_dfu_end as *const u32 as usize, + ) + }; + let state = unsafe { + Partition::new( + &__bootloader_state_start as *const u32 as usize, + &__bootloader_state_end as *const u32 as usize, + ) + }; + + trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); + trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); + trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); + + Self::new(active, dfu, state) + } +} diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index d0be6d195..fa74be8c4 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -20,7 +20,7 @@ nightly = ["embedded-hal-async", "embedded-storage-async"] embassy-sync = { version = "0.1.0", path = "../embassy-sync" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } -embedded-hal-async = { version = "=0.1.0-alpha.2", optional = true } +embedded-hal-async = { version = "=0.2.0-alpha.0", optional = true } embedded-storage = "0.3.0" embedded-storage-async = { version = "0.3.0", optional = true } nb = "1.0.0" diff --git a/embassy-embedded-hal/src/adapter.rs b/embassy-embedded-hal/src/adapter.rs index 1c43f015f..3680984f1 100644 --- a/embassy-embedded-hal/src/adapter.rs +++ b/embassy-embedded-hal/src/adapter.rs @@ -38,32 +38,31 @@ where E: embedded_hal_1::i2c::Error + 'static, T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { self.wrapped.read(address, buffer) } + async fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, buffer) } - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - async move { self.wrapped.write(address, bytes) } + async fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Result<(), Self::Error> { + self.wrapped.write(address, bytes) } - fn write_read<'a>(&'a mut self, address: u8, bytes: &'a [u8], buffer: &'a mut [u8]) -> Self::WriteReadFuture<'a> { - async move { self.wrapped.write_read(address, bytes, buffer) } + async fn write_read<'a>( + &'a mut self, + address: u8, + bytes: &'a [u8], + buffer: &'a mut [u8], + ) -> Result<(), Self::Error> { + self.wrapped.write_read(address, bytes, buffer) } - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( + async fn transaction<'a, 'b>( &'a mut self, address: u8, operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { + ) -> Result<(), Self::Error> { let _ = address; let _ = operations; - async move { todo!() } + todo!() } } @@ -84,23 +83,17 @@ where E: embedded_hal_1::spi::Error + 'static, T: blocking::spi::Transfer + blocking::spi::Write, { - type TransferFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Self::TransferFuture<'a> { - async move { - // Ensure we write the expected bytes - for i in 0..core::cmp::min(read.len(), write.len()) { - read[i] = write[i].clone(); - } - self.wrapped.transfer(read)?; - Ok(()) + async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> { + // Ensure we write the expected bytes + for i in 0..core::cmp::min(read.len(), write.len()) { + read[i] = write[i].clone(); } + self.wrapped.transfer(read)?; + Ok(()) } - type TransferInPlaceFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Self::TransferInPlaceFuture<'a> { - async move { todo!() } + async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> { + todo!() } } @@ -109,10 +102,8 @@ where E: embedded_hal_1::spi::Error + 'static, T: blocking::spi::Transfer + blocking::spi::Write, { - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } @@ -121,13 +112,9 @@ where E: embedded_hal_1::spi::Error + 'static, T: blocking::spi::Transfer + blocking::spi::Write, { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - self.wrapped.write(data)?; - Ok(()) - } + async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(data)?; + Ok(()) } } @@ -136,13 +123,9 @@ where E: embedded_hal_1::spi::Error + 'static, T: blocking::spi::Transfer + blocking::spi::Write, { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, data: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - self.wrapped.transfer(data)?; - Ok(()) - } + async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.transfer(data)?; + Ok(()) } } @@ -192,7 +175,7 @@ where } type FlushFuture<'a> = impl Future> + 'a where T: 'a; - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { + fn flush(&mut self) -> Result<(), Self::Error> { async move { self.wrapped.bflush() } } } diff --git a/embassy-embedded-hal/src/lib.rs b/embassy-embedded-hal/src/lib.rs index a12a3a3a0..8da042228 100644 --- a/embassy-embedded-hal/src/lib.rs +++ b/embassy-embedded-hal/src/lib.rs @@ -1,5 +1,9 @@ #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] +#![cfg_attr( + feature = "nightly", + feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections) +)] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] #![warn(missing_docs)] //! Utilities to use `embedded-hal` traits with Embassy. diff --git a/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs b/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs index 0bc6afd98..c5e1fd415 100644 --- a/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs +++ b/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs @@ -22,7 +22,6 @@ //! let i2c_dev2 = I2cDevice::new(i2c_bus); //! let mpu = Mpu6050::new(i2c_dev2); //! ``` -use core::future::Future; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; @@ -55,53 +54,39 @@ where M: RawMutex + 'static, BUS: i2c::I2c + 'static, { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write_read<'a>( + async fn write_read<'a>( &'a mut self, address: u8, wr_buffer: &'a [u8], rd_buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.write_read(address, wr_buffer, rd_buffer) - .await - .map_err(I2cDeviceError::I2c)?; - Ok(()) - } + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write_read(address, wr_buffer, rd_buffer) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) } - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( + async fn transaction<'a, 'b>( &'a mut self, address: u8, operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { + ) -> Result<(), I2cDeviceError> { let _ = address; let _ = operations; - async move { todo!() } + todo!() } } @@ -136,55 +121,41 @@ where M: RawMutex + 'static, BUS: i2c::I2c + SetConfig + 'static, { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write_read<'a>( + async fn write_read<'a>( &'a mut self, address: u8, wr_buffer: &'a [u8], rd_buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - bus.write_read(address, wr_buffer, rd_buffer) - .await - .map_err(I2cDeviceError::I2c)?; - Ok(()) - } + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + bus.write_read(address, wr_buffer, rd_buffer) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) } - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( + async fn transaction<'a, 'b>( &'a mut self, address: u8, operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { + ) -> Result<(), I2cDeviceError> { let _ = address; let _ = operations; - async move { todo!() } + todo!() } } diff --git a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs index a3814d6d0..d25716655 100644 --- a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs +++ b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs @@ -65,33 +65,25 @@ where { type Bus = BUS; - type TransactionFuture<'a, R, F, Fut> = impl Future> + 'a + async fn transaction(&mut self, f: F) -> Result where - Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a; - - fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> - where - R: 'a, - F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a, + F: FnOnce(*mut Self::Bus) -> Fut, + Fut: Future::Error>>, { - async move { - let mut bus = self.bus.lock().await; - self.cs.set_low().map_err(SpiDeviceError::Cs)?; + let mut bus = self.bus.lock().await; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; - let f_res = f(&mut *bus).await; + let f_res = f(&mut *bus).await; - // On failure, it's important to still flush and deassert CS. - let flush_res = bus.flush().await; - let cs_res = self.cs.set_high(); + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush().await; + let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(SpiDeviceError::Spi)?; - flush_res.map_err(SpiDeviceError::Spi)?; - cs_res.map_err(SpiDeviceError::Cs)?; + let f_res = f_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; - Ok(f_res) - } + Ok(f_res) } } @@ -130,33 +122,25 @@ where { type Bus = BUS; - type TransactionFuture<'a, R, F, Fut> = impl Future> + 'a + async fn transaction(&mut self, f: F) -> Result where - Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a; - - fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> - where - R: 'a, - F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a, + F: FnOnce(*mut Self::Bus) -> Fut, + Fut: Future::Error>>, { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - self.cs.set_low().map_err(SpiDeviceError::Cs)?; + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + self.cs.set_low().map_err(SpiDeviceError::Cs)?; - let f_res = f(&mut *bus).await; + let f_res = f(&mut *bus).await; - // On failure, it's important to still flush and deassert CS. - let flush_res = bus.flush().await; - let cs_res = self.cs.set_high(); + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush().await; + let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(SpiDeviceError::Spi)?; - flush_res.map_err(SpiDeviceError::Spi)?; - cs_res.map_err(SpiDeviceError::Cs)?; + let f_res = f_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; - Ok(f_res) - } + Ok(f_res) } } diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index d0f51646d..c2868eb98 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -1,9 +1,15 @@ [package] name = "embassy-executor" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "MIT OR Apache-2.0" - +description = "async/await executor designed for embedded usage" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" @@ -21,10 +27,13 @@ flavors = [ { name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] }, ] +[package.metadata.docs.rs] +features = ["std", "nightly", "defmt"] + [features] default = [] -std = ["embassy-macros/std", "critical-section/std"] -wasm = ["dep:wasm-bindgen", "dep:js-sys", "embassy-macros/wasm"] +std = ["critical-section/std"] +wasm = ["dep:wasm-bindgen", "dep:js-sys"] # Enable nightly-only features nightly = [] diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index 2a4b006da..e97a56cda 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -1,7 +1,6 @@ use core::marker::PhantomData; use core::ptr; - -use atomic_polyfill::{AtomicBool, Ordering}; +use core::sync::atomic::{AtomicBool, Ordering}; use super::{raw, Spawner}; diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index f908aaa70..4ee0d9f78 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -1,7 +1,6 @@ use core::marker::PhantomData; use core::ptr; - -use atomic_polyfill::{AtomicBool, Ordering}; +use core::sync::atomic::{AtomicBool, Ordering}; use super::{raw, Spawner}; diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index e4cbd04b9..4c7e2f4cd 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -8,18 +8,22 @@ pub(crate) mod fmt; #[cfg(feature = "nightly")] -pub use embassy_macros::{main, task}; +pub use embassy_macros::task; cfg_if::cfg_if! { if #[cfg(cortex_m)] { #[path="arch/cortex_m.rs"] mod arch; pub use arch::*; + #[cfg(feature = "nightly")] + pub use embassy_macros::main_cortex_m as main; } else if #[cfg(target_arch="riscv32")] { #[path="arch/riscv32.rs"] mod arch; pub use arch::*; + #[cfg(feature = "nightly")] + pub use embassy_macros::main_riscv as main; } else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] { #[path="arch/xtensa.rs"] @@ -30,11 +34,15 @@ cfg_if::cfg_if! { #[path="arch/wasm.rs"] mod arch; pub use arch::*; + #[cfg(feature = "nightly")] + pub use embassy_macros::main_wasm as main; } else if #[cfg(feature="std")] { #[path="arch/std.rs"] mod arch; pub use arch::*; + #[cfg(feature = "nightly")] + pub use embassy_macros::main_std as main; } } diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index e1258ebb5..42bd82262 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -15,10 +15,10 @@ mod waker; use core::cell::Cell; use core::future::Future; +use core::mem; use core::pin::Pin; use core::ptr::NonNull; use core::task::{Context, Poll}; -use core::{mem, ptr}; use atomic_polyfill::{AtomicU32, Ordering}; use critical_section::CriticalSection; @@ -43,14 +43,11 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; /// Raw task header for use in task pointers. -/// -/// This is an opaque struct, used for raw pointers to tasks, for use -/// with funtions like [`wake_task`] and [`task_from_waker`]. -pub struct TaskHeader { +pub(crate) struct TaskHeader { pub(crate) state: AtomicU32, pub(crate) run_queue_item: RunQueueItem, - pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 - pub(crate) poll_fn: UninitCell)>, // Valid if STATE_SPAWNED + pub(crate) executor: Cell>, + poll_fn: Cell>, #[cfg(feature = "integrated-timers")] pub(crate) expires_at: Cell, @@ -58,20 +55,34 @@ pub struct TaskHeader { pub(crate) timer_queue_item: timer_queue::TimerQueueItem, } -impl TaskHeader { - pub(crate) const fn new() -> Self { - Self { - state: AtomicU32::new(0), - run_queue_item: RunQueueItem::new(), - executor: Cell::new(ptr::null()), - poll_fn: UninitCell::uninit(), +/// This is essentially a `&'static TaskStorage` where the type of the future has been erased. +#[derive(Clone, Copy)] +pub struct TaskRef { + ptr: NonNull, +} - #[cfg(feature = "integrated-timers")] - expires_at: Cell::new(Instant::from_ticks(0)), - #[cfg(feature = "integrated-timers")] - timer_queue_item: timer_queue::TimerQueueItem::new(), +impl TaskRef { + fn new(task: &'static TaskStorage) -> Self { + Self { + ptr: NonNull::from(task).cast(), } } + + /// Safety: The pointer must have been obtained with `Task::as_ptr` + pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self { + Self { + ptr: NonNull::new_unchecked(ptr as *mut TaskHeader), + } + } + + pub(crate) fn header(self) -> &'static TaskHeader { + unsafe { self.ptr.as_ref() } + } + + /// The returned pointer is valid for the entire TaskStorage. + pub(crate) fn as_ptr(self) -> *const TaskHeader { + self.ptr.as_ptr() + } } /// Raw storage in which a task can be spawned. @@ -101,7 +112,18 @@ impl TaskStorage { /// Create a new TaskStorage, in not-spawned state. pub const fn new() -> Self { Self { - raw: TaskHeader::new(), + raw: TaskHeader { + state: AtomicU32::new(0), + run_queue_item: RunQueueItem::new(), + executor: Cell::new(None), + // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` + poll_fn: Cell::new(None), + + #[cfg(feature = "integrated-timers")] + expires_at: Cell::new(Instant::from_ticks(0)), + #[cfg(feature = "integrated-timers")] + timer_queue_item: timer_queue::TimerQueueItem::new(), + }, future: UninitCell::uninit(), } } @@ -120,29 +142,17 @@ impl TaskStorage { /// Once the task has finished running, you may spawn it again. It is allowed to spawn it /// on a different executor. pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - if self.spawn_mark_used() { - return unsafe { SpawnToken::::new(self.spawn_initialize(future)) }; + let task = AvailableTask::claim(self); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } + } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } - fn spawn_mark_used(&'static self) -> bool { - let state = STATE_SPAWNED | STATE_RUN_QUEUED; - self.raw - .state - .compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire) - .is_ok() - } - - unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull { - // Initialize the task - self.raw.poll_fn.write(Self::poll); - self.future.write(future()); - NonNull::new_unchecked(self as *const TaskStorage as *const TaskHeader as *mut TaskHeader) - } - - unsafe fn poll(p: NonNull) { + unsafe fn poll(p: TaskRef) { let this = &*(p.as_ptr() as *const TaskStorage); let future = Pin::new_unchecked(this.future.as_mut()); @@ -164,6 +174,28 @@ impl TaskStorage { unsafe impl Sync for TaskStorage {} +struct AvailableTask { + task: &'static TaskStorage, +} + +impl AvailableTask { + fn claim(task: &'static TaskStorage) -> Option { + task.raw + .state + .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) + .ok() + .map(|_| Self { task }) + } + + fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { + unsafe { + self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); + self.task.future.write(future()); + } + TaskRef::new(self.task) + } +} + /// Raw storage that can hold up to N tasks of the same type. /// /// This is essentially a `[TaskStorage; N]`. @@ -187,13 +219,14 @@ impl TaskPool { /// is currently free. If none is free, a "poisoned" SpawnToken is returned, /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - for task in &self.pool { - if task.spawn_mark_used() { - return unsafe { SpawnToken::::new(task.spawn_initialize(future)) }; + let task = self.pool.iter().find_map(AvailableTask::claim); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } /// Like spawn(), but allows the task to be send-spawned if the args are Send even if @@ -235,13 +268,14 @@ impl TaskPool { // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. - for task in &self.pool { - if task.spawn_mark_used() { - return SpawnToken::::new(task.spawn_initialize(future)); + let task = self.pool.iter().find_map(AvailableTask::claim); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } } @@ -307,7 +341,7 @@ impl Executor { /// - `task` must be set up to run in this executor. /// - `task` must NOT be already enqueued (in this executor or another one). #[inline(always)] - unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull) { + unsafe fn enqueue(&self, cs: CriticalSection, task: TaskRef) { #[cfg(feature = "rtos-trace")] trace::task_ready_begin(task.as_ptr() as u32); @@ -325,8 +359,8 @@ impl Executor { /// It is OK to use `unsafe` to call this from a thread that's not the executor thread. /// In this case, the task's Future must be Send. This is because this is effectively /// sending the task to the executor thread. - pub(super) unsafe fn spawn(&'static self, task: NonNull) { - task.as_ref().executor.set(self); + pub(super) unsafe fn spawn(&'static self, task: TaskRef) { + task.header().executor.set(Some(self)); #[cfg(feature = "rtos-trace")] trace::task_new(task.as_ptr() as u32); @@ -354,46 +388,54 @@ impl Executor { /// somehow schedule for `poll()` to be called later, at a time you know for sure there's /// no `poll()` already running. pub unsafe fn poll(&'static self) { - #[cfg(feature = "integrated-timers")] - self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); + loop { + #[cfg(feature = "integrated-timers")] + self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); - self.run_queue.dequeue_all(|p| { - let task = p.as_ref(); + self.run_queue.dequeue_all(|p| { + let task = p.header(); + + #[cfg(feature = "integrated-timers")] + task.expires_at.set(Instant::MAX); + + let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); + if state & STATE_SPAWNED == 0 { + // If task is not running, ignore it. This can happen in the following scenario: + // - Task gets dequeued, poll starts + // - While task is being polled, it gets woken. It gets placed in the queue. + // - Task poll finishes, returning done=true + // - RUNNING bit is cleared, but the task is already in the queue. + return; + } + + #[cfg(feature = "rtos-trace")] + trace::task_exec_begin(p.as_ptr() as u32); + + // Run the task + task.poll_fn.get().unwrap_unchecked()(p); + + #[cfg(feature = "rtos-trace")] + trace::task_exec_end(); + + // Enqueue or update into timer_queue + #[cfg(feature = "integrated-timers")] + self.timer_queue.update(p); + }); #[cfg(feature = "integrated-timers")] - task.expires_at.set(Instant::MAX); - - let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); - if state & STATE_SPAWNED == 0 { - // If task is not running, ignore it. This can happen in the following scenario: - // - Task gets dequeued, poll starts - // - While task is being polled, it gets woken. It gets placed in the queue. - // - Task poll finishes, returning done=true - // - RUNNING bit is cleared, but the task is already in the queue. - return; + { + // If this is already in the past, set_alarm might return false + // In that case do another poll loop iteration. + let next_expiration = self.timer_queue.next_expiration(); + if driver::set_alarm(self.alarm, next_expiration.as_ticks()) { + break; + } } - #[cfg(feature = "rtos-trace")] - trace::task_exec_begin(p.as_ptr() as u32); - - // Run the task - task.poll_fn.read()(p as _); - - #[cfg(feature = "rtos-trace")] - trace::task_exec_end(); - - // Enqueue or update into timer_queue - #[cfg(feature = "integrated-timers")] - self.timer_queue.update(p); - }); - - #[cfg(feature = "integrated-timers")] - { - // If this is already in the past, set_alarm will immediately trigger the alarm. - // This will cause `signal_fn` to be called, which will cause `poll()` to be called again, - // so we immediately do another poll loop iteration. - let next_expiration = self.timer_queue.next_expiration(); - driver::set_alarm(self.alarm, next_expiration.as_ticks()); + #[cfg(not(feature = "integrated-timers"))] + { + break; + } } #[cfg(feature = "rtos-trace")] @@ -409,16 +451,12 @@ impl Executor { } } -/// Wake a task by raw pointer. +/// Wake a task by `TaskRef`. /// -/// You can obtain task pointers from `Waker`s using [`task_from_waker`]. -/// -/// # Safety -/// -/// `task` must be a valid task pointer obtained from [`task_from_waker`]. -pub unsafe fn wake_task(task: NonNull) { +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task(task: TaskRef) { critical_section::with(|cs| { - let header = task.as_ref(); + let header = task.header(); let state = header.state.load(Ordering::Relaxed); // If already scheduled, or if not started, @@ -430,20 +468,29 @@ pub unsafe fn wake_task(task: NonNull) { header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed); // We have just marked the task as scheduled, so enqueue it. - let executor = &*header.executor.get(); - executor.enqueue(cs, task); + unsafe { + let executor = header.executor.get().unwrap_unchecked(); + executor.enqueue(cs, task); + } }) } #[cfg(feature = "integrated-timers")] -#[no_mangle] -unsafe fn _embassy_time_schedule_wake(at: Instant, waker: &core::task::Waker) { - let task = waker::task_from_waker(waker); - let task = task.as_ref(); - let expires_at = task.expires_at.get(); - task.expires_at.set(expires_at.min(at)); +struct TimerQueue; + +#[cfg(feature = "integrated-timers")] +impl embassy_time::queue::TimerQueue for TimerQueue { + fn schedule_wake(&'static self, at: Instant, waker: &core::task::Waker) { + let task = waker::task_from_waker(waker); + let task = task.header(); + let expires_at = task.expires_at.get(); + task.expires_at.set(expires_at.min(at)); + } } +#[cfg(feature = "integrated-timers")] +embassy_time::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue); + #[cfg(feature = "rtos-trace")] impl rtos_trace::RtosTraceOSCallbacks for Executor { fn task_list() { diff --git a/embassy-executor/src/raw/run_queue.rs b/embassy-executor/src/raw/run_queue.rs index ed8c82a5c..362157535 100644 --- a/embassy-executor/src/raw/run_queue.rs +++ b/embassy-executor/src/raw/run_queue.rs @@ -4,7 +4,7 @@ use core::ptr::NonNull; use atomic_polyfill::{AtomicPtr, Ordering}; use critical_section::CriticalSection; -use super::TaskHeader; +use super::{TaskHeader, TaskRef}; pub(crate) struct RunQueueItem { next: AtomicPtr, @@ -46,25 +46,26 @@ impl RunQueue { /// /// `item` must NOT be already enqueued in any queue. #[inline(always)] - pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull) -> bool { + pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: TaskRef) -> bool { let prev = self.head.load(Ordering::Relaxed); - task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed); - self.head.store(task.as_ptr(), Ordering::Relaxed); + task.header().run_queue_item.next.store(prev, Ordering::Relaxed); + self.head.store(task.as_ptr() as _, Ordering::Relaxed); prev.is_null() } /// Empty the queue, then call `on_task` for each task that was in the queue. /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. - pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull)) { + pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { // Atomically empty the queue. let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); // Iterate the linked list of tasks that were previously in the queue. while let Some(task) = NonNull::new(ptr) { + let task = unsafe { TaskRef::from_ptr(task.as_ptr()) }; // If the task re-enqueues itself, the `next` pointer will get overwritten. // Therefore, first read the next pointer, and only then process the task. - let next = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed); + let next = task.header().run_queue_item.next.load(Ordering::Relaxed); on_task(task); diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs index 24c31892a..57d6d3cda 100644 --- a/embassy-executor/src/raw/timer_queue.rs +++ b/embassy-executor/src/raw/timer_queue.rs @@ -1,45 +1,39 @@ use core::cell::Cell; use core::cmp::min; -use core::ptr; -use core::ptr::NonNull; use atomic_polyfill::Ordering; use embassy_time::Instant; -use super::{TaskHeader, STATE_TIMER_QUEUED}; +use super::{TaskRef, STATE_TIMER_QUEUED}; pub(crate) struct TimerQueueItem { - next: Cell<*mut TaskHeader>, + next: Cell>, } impl TimerQueueItem { pub const fn new() -> Self { - Self { - next: Cell::new(ptr::null_mut()), - } + Self { next: Cell::new(None) } } } pub(crate) struct TimerQueue { - head: Cell<*mut TaskHeader>, + head: Cell>, } impl TimerQueue { pub const fn new() -> Self { - Self { - head: Cell::new(ptr::null_mut()), - } + Self { head: Cell::new(None) } } - pub(crate) unsafe fn update(&self, p: NonNull) { - let task = p.as_ref(); + pub(crate) unsafe fn update(&self, p: TaskRef) { + let task = p.header(); if task.expires_at.get() != Instant::MAX { let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); let is_new = old_state & STATE_TIMER_QUEUED == 0; if is_new { task.timer_queue_item.next.set(self.head.get()); - self.head.set(p.as_ptr()); + self.head.set(Some(p)); } } } @@ -47,7 +41,7 @@ impl TimerQueue { pub(crate) unsafe fn next_expiration(&self) -> Instant { let mut res = Instant::MAX; self.retain(|p| { - let task = p.as_ref(); + let task = p.header(); let expires = task.expires_at.get(); res = min(res, expires); expires != Instant::MAX @@ -55,9 +49,9 @@ impl TimerQueue { res } - pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull)) { + pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(TaskRef)) { self.retain(|p| { - let task = p.as_ref(); + let task = p.header(); if task.expires_at.get() <= now { on_task(p); false @@ -67,11 +61,10 @@ impl TimerQueue { }); } - pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull) -> bool) { + pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { let mut prev = &self.head; - while !prev.get().is_null() { - let p = NonNull::new_unchecked(prev.get()); - let task = &*p.as_ptr(); + while let Some(p) = prev.get() { + let task = p.header(); if f(p) { // Skip to next prev = &task.timer_queue_item.next; diff --git a/embassy-executor/src/raw/util.rs b/embassy-executor/src/raw/util.rs index ed5822188..2b1f6b6f3 100644 --- a/embassy-executor/src/raw/util.rs +++ b/embassy-executor/src/raw/util.rs @@ -25,9 +25,3 @@ impl UninitCell { ptr::drop_in_place(self.as_mut_ptr()) } } - -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) - } -} diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs index 5765259f2..400b37fa9 100644 --- a/embassy-executor/src/raw/waker.rs +++ b/embassy-executor/src/raw/waker.rs @@ -1,8 +1,7 @@ use core::mem; -use core::ptr::NonNull; use core::task::{RawWaker, RawWakerVTable, Waker}; -use super::{wake_task, TaskHeader}; +use super::{wake_task, TaskHeader, TaskRef}; const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); @@ -11,14 +10,14 @@ unsafe fn clone(p: *const ()) -> RawWaker { } unsafe fn wake(p: *const ()) { - wake_task(NonNull::new_unchecked(p as *mut TaskHeader)) + wake_task(TaskRef::from_ptr(p as *const TaskHeader)) } unsafe fn drop(_: *const ()) { // nop } -pub(crate) unsafe fn from_task(p: NonNull) -> Waker { +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) } @@ -33,7 +32,7 @@ pub(crate) unsafe fn from_task(p: NonNull) -> Waker { /// # Panics /// /// Panics if the waker is not created by the Embassy executor. -pub fn task_from_waker(waker: &Waker) -> NonNull { +pub fn task_from_waker(waker: &Waker) -> TaskRef { // safety: OK because WakerHack has the same layout as Waker. // This is not really guaranteed because the structs are `repr(Rust)`, it is // indeed the case in the current implementation. @@ -43,8 +42,8 @@ pub fn task_from_waker(waker: &Waker) -> NonNull { panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") } - // safety: we never create a waker with a null data pointer. - unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) } + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(hack.data as *const TaskHeader) } } struct WakerHack { diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 400d973ff..7c0a0183c 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -1,7 +1,6 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::mem; -use core::ptr::NonNull; use core::task::Poll; use super::raw; @@ -22,12 +21,12 @@ use super::raw; /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] pub struct SpawnToken { - raw_task: Option>, + raw_task: Option, phantom: PhantomData<*mut S>, } impl SpawnToken { - pub(crate) unsafe fn new(raw_task: NonNull) -> Self { + pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self { Self { raw_task: Some(raw_task), phantom: PhantomData, @@ -90,10 +89,10 @@ impl Spawner { /// /// Panics if the current executor is not an Embassy executor. pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { + poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); - Poll::Ready(Self::new(&*executor)) + let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + Poll::Ready(Self::new(executor)) }) .await } @@ -166,10 +165,10 @@ impl SendSpawner { /// /// Panics if the current executor is not an Embassy executor. pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { + poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); - Poll::Ready(Self::new(&*executor)) + let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + Poll::Ready(Self::new(executor)) }) .await } diff --git a/embassy-hal-common/src/atomic_ring_buffer.rs b/embassy-hal-common/src/atomic_ring_buffer.rs new file mode 100644 index 000000000..4c944d763 --- /dev/null +++ b/embassy-hal-common/src/atomic_ring_buffer.rs @@ -0,0 +1,371 @@ +use core::slice; +use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; + +/// Atomic reusable ringbuffer +/// +/// This ringbuffer implementation is designed to be stored in a `static`, +/// therefore all methods take `&self` and not `&mut self`. +/// +/// It is "reusable": when created it has no backing buffer, you can give it +/// one with `init` and take it back with `deinit`, and init it again in the +/// future if needed. This is very non-idiomatic, but helps a lot when storing +/// it in a `static`. +/// +/// One concurrent writer and one concurrent reader are supported, even at +/// different execution priorities (like main and irq). +pub struct RingBuffer { + buf: AtomicPtr, + len: AtomicUsize, + start: AtomicUsize, + end: AtomicUsize, +} + +pub struct Reader<'a>(&'a RingBuffer); +pub struct Writer<'a>(&'a RingBuffer); + +impl RingBuffer { + /// Create a new empty ringbuffer. + pub const fn new() -> Self { + Self { + buf: AtomicPtr::new(core::ptr::null_mut()), + len: AtomicUsize::new(0), + start: AtomicUsize::new(0), + end: AtomicUsize::new(0), + } + } + + /// Initialize the ring buffer with a buffer. + /// + /// # Safety + /// - The buffer (`buf .. buf+len`) must be valid memory until `deinit` is called. + /// - Must not be called concurrently with any other methods. + pub unsafe fn init(&self, buf: *mut u8, len: usize) { + // Ordering: it's OK to use `Relaxed` because this is not called + // concurrently with other methods. + self.buf.store(buf, Ordering::Relaxed); + self.len.store(len, Ordering::Relaxed); + self.start.store(0, Ordering::Relaxed); + self.end.store(0, Ordering::Relaxed); + } + + /// Deinitialize the ringbuffer. + /// + /// After calling this, the ringbuffer becomes empty, as if it was + /// just created with `new()`. + /// + /// # Safety + /// - Must not be called concurrently with any other methods. + pub unsafe fn deinit(&self) { + // Ordering: it's OK to use `Relaxed` because this is not called + // concurrently with other methods. + self.len.store(0, Ordering::Relaxed); + self.start.store(0, Ordering::Relaxed); + self.end.store(0, Ordering::Relaxed); + } + + /// Create a reader. + /// + /// # Safety + /// + /// Only one reader can exist at a time. + pub unsafe fn reader(&self) -> Reader<'_> { + Reader(self) + } + + /// Create a writer. + /// + /// # Safety + /// + /// Only one writer can exist at a time. + pub unsafe fn writer(&self) -> Writer<'_> { + Writer(self) + } + + pub fn len(&self) -> usize { + self.len.load(Ordering::Relaxed) + } + + pub fn is_full(&self) -> bool { + let len = self.len.load(Ordering::Relaxed); + let start = self.start.load(Ordering::Relaxed); + let end = self.end.load(Ordering::Relaxed); + + len == 0 || self.wrap(end + 1) == start + } + + pub fn is_empty(&self) -> bool { + let start = self.start.load(Ordering::Relaxed); + let end = self.end.load(Ordering::Relaxed); + + start == end + } + + fn wrap(&self, n: usize) -> usize { + let len = self.len.load(Ordering::Relaxed); + + assert!(n <= len); + if n == len { + 0 + } else { + n + } + } +} + +impl<'a> Writer<'a> { + /// Push data into the buffer in-place. + /// + /// The closure `f` is called with a free part of the buffer, it must write + /// some data to it and return the amount of bytes written. + pub fn push(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize { + let (p, n) = self.push_buf(); + let buf = unsafe { slice::from_raw_parts_mut(p, n) }; + let n = f(buf); + self.push_done(n); + n + } + + /// Push one data byte. + /// + /// Returns true if pushed succesfully. + pub fn push_one(&mut self, val: u8) -> bool { + let n = self.push(|f| match f { + [] => 0, + [x, ..] => { + *x = val; + 1 + } + }); + n != 0 + } + + /// Get a buffer where data can be pushed to. + /// + /// Equivalent to [`Self::push_buf`] but returns a slice. + pub fn push_slice(&mut self) -> &mut [u8] { + let (data, len) = self.push_buf(); + unsafe { slice::from_raw_parts_mut(data, len) } + } + + /// Get a buffer where data can be pushed to. + /// + /// Write data to the start of the buffer, then call `push_done` with + /// however many bytes you've pushed. + /// + /// The buffer is suitable to DMA to. + /// + /// If the ringbuf is full, size=0 will be returned. + /// + /// The buffer stays valid as long as no other `Writer` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn push_buf(&mut self) -> (*mut u8, usize) { + // Ordering: popping writes `start` last, so we read `start` first. + // Read it with Acquire ordering, so that the next accesses can't be reordered up past it. + let start = self.0.start.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let end = self.0.end.load(Ordering::Relaxed); + + let n = if start <= end { + len - end - (start == 0 && len != 0) as usize + } else { + start - end - 1 + }; + + trace!(" ringbuf: push_buf {:?}..{:?}", end, end + n); + (unsafe { buf.add(end) }, n) + } + + pub fn push_done(&mut self, n: usize) { + trace!(" ringbuf: push {:?}", n); + let end = self.0.end.load(Ordering::Relaxed); + + // Ordering: write `end` last, with Release ordering. + // The ordering ensures no preceding memory accesses (such as writing + // the actual data in the buffer) can be reordered down past it, which + // will guarantee the reader sees them after reading from `end`. + self.0.end.store(self.0.wrap(end + n), Ordering::Release); + } +} + +impl<'a> Reader<'a> { + /// Pop data from the buffer in-place. + /// + /// The closure `f` is called with the next data, it must process + /// some data from it and return the amount of bytes processed. + pub fn pop(&mut self, f: impl FnOnce(&[u8]) -> usize) -> usize { + let (p, n) = self.pop_buf(); + let buf = unsafe { slice::from_raw_parts(p, n) }; + let n = f(buf); + self.pop_done(n); + n + } + + /// Pop one data byte. + /// + /// Returns true if popped succesfully. + pub fn pop_one(&mut self) -> Option { + let mut res = None; + self.pop(|f| match f { + &[] => 0, + &[x, ..] => { + res = Some(x); + 1 + } + }); + res + } + + /// Get a buffer where data can be popped from. + /// + /// Equivalent to [`Self::pop_buf`] but returns a slice. + pub fn pop_slice(&mut self) -> &mut [u8] { + let (data, len) = self.pop_buf(); + unsafe { slice::from_raw_parts_mut(data, len) } + } + + /// Get a buffer where data can be popped from. + /// + /// Read data from the start of the buffer, then call `pop_done` with + /// however many bytes you've processed. + /// + /// The buffer is suitable to DMA from. + /// + /// If the ringbuf is empty, size=0 will be returned. + /// + /// The buffer stays valid as long as no other `Reader` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn pop_buf(&mut self) -> (*mut u8, usize) { + // Ordering: pushing writes `end` last, so we read `end` first. + // Read it with Acquire ordering, so that the next accesses can't be reordered up past it. + // This is needed to guarantee we "see" the data written by the writer. + let end = self.0.end.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let start = self.0.start.load(Ordering::Relaxed); + + let n = if end < start { len - start } else { end - start }; + + trace!(" ringbuf: pop_buf {:?}..{:?}", start, start + n); + (unsafe { buf.add(start) }, n) + } + + pub fn pop_done(&mut self, n: usize) { + trace!(" ringbuf: pop {:?}", n); + + let start = self.0.start.load(Ordering::Relaxed); + + // Ordering: write `start` last, with Release ordering. + // The ordering ensures no preceding memory accesses (such as reading + // the actual data) can be reordered down past it. This is necessary + // because writing to `start` is effectively freeing the read part of the + // buffer, which "gives permission" to the writer to write to it again. + // Therefore, all buffer accesses must be completed before this. + self.0.start.store(self.0.wrap(start + n), Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn push_pop() { + let mut b = [0; 4]; + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), 4); + + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), false); + + rb.writer().push(|buf| { + // If capacity is 4, we can fill it up to 3. + assert_eq!(3, buf.len()); + buf[0] = 1; + buf[1] = 2; + buf[2] = 3; + 3 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + + rb.writer().push(|buf| { + // If it's full, we can push 0 bytes. + assert_eq!(0, buf.len()); + 0 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + assert_eq!(1, buf[0]); + 1 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + 0 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + assert_eq!(2, buf[0]); + assert_eq!(3, buf[1]); + 2 + }); + + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + + rb.writer().push(|buf| { + assert_eq!(1, buf.len()); + buf[0] = 10; + 1 + }); + + rb.writer().push(|buf| { + assert_eq!(2, buf.len()); + buf[0] = 11; + buf[1] = 12; + 2 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + } + } + + #[test] + fn zero_len() { + let rb = RingBuffer::new(); + unsafe { + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), true); + + rb.writer().push(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + + rb.reader().pop(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + } + } +} diff --git a/embassy-hal-common/src/lib.rs b/embassy-hal-common/src/lib.rs index 5d2649d02..b2a35cd35 100644 --- a/embassy-hal-common/src/lib.rs +++ b/embassy-hal-common/src/lib.rs @@ -4,6 +4,7 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +pub mod atomic_ring_buffer; pub mod drop; mod macros; mod peripheral; diff --git a/embassy-hal-common/src/macros.rs b/embassy-hal-common/src/macros.rs index 7af85f782..da7913136 100644 --- a/embassy-hal-common/src/macros.rs +++ b/embassy-hal-common/src/macros.rs @@ -1,10 +1,12 @@ #[macro_export] macro_rules! peripherals { ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + /// Types for the peripheral singletons. pub mod peripherals { $( $(#[$cfg])? #[allow(non_camel_case_types)] + #[doc = concat!(stringify!($name), " peripheral")] pub struct $name { _private: () } $(#[$cfg])? @@ -25,9 +27,13 @@ macro_rules! peripherals { )* } + /// Struct containing all the peripheral singletons. + /// + /// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]. #[allow(non_snake_case)] pub struct Peripherals { $( + #[doc = concat!(stringify!($name), " peripheral")] $(#[$cfg])? pub $name: peripherals::$name, )* diff --git a/embassy-hal-common/src/peripheral.rs b/embassy-hal-common/src/peripheral.rs index f507468f8..4a6b6a600 100644 --- a/embassy-hal-common/src/peripheral.rs +++ b/embassy-hal-common/src/peripheral.rs @@ -3,16 +3,17 @@ use core::ops::{Deref, DerefMut}; /// An exclusive reference to a peripheral. /// -/// This is functionally the same as a `&'a mut T`. The reason for having a -/// dedicated struct is memory efficiency: +/// This is functionally the same as a `&'a mut T`. There's a few advantages in having +/// a dedicated struct instead: /// -/// Peripheral singletons are typically either zero-sized (for concrete peripherals -/// like `PA9` or `Spi4`) or very small (for example `AnyPin` which is 1 byte). -/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. -/// PeripheralRef stores a copy of `T` instead, so it's the same size. -/// -/// but it is the size of `T` not the size -/// of a pointer. This is useful if T is a zero sized type. +/// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete +/// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte). +/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. +/// PeripheralRef stores a copy of `T` instead, so it's the same size. +/// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`, +/// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic +/// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes +/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization. pub struct PeripheralRef<'a, T> { inner: T, _lifetime: PhantomData<&'a mut T>, diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml index 0e7a982a1..cbe78e592 100644 --- a/embassy-lora/Cargo.toml +++ b/embassy-lora/Cargo.toml @@ -9,6 +9,7 @@ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/em src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/" features = ["time", "defmt"] flavors = [ + { name = "sx126x", target = "thumbv7em-none-eabihf", features = ["sx126x"] }, { name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] }, { name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] }, ] @@ -16,6 +17,7 @@ flavors = [ [lib] [features] +sx126x = [] sx127x = [] stm32wl = ["embassy-stm32", "embassy-stm32/subghz"] time = [] @@ -30,7 +32,7 @@ embassy-time = { version = "0.1.0", path = "../embassy-time" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" } embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } -embedded-hal-async = { version = "=0.1.0-alpha.2" } +embedded-hal-async = { version = "=0.2.0-alpha.0" } embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } embedded-hal = { version = "0.2", features = ["unproven"] } diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs index 90ba0d1d4..3e4748430 100644 --- a/embassy-lora/src/lib.rs +++ b/embassy-lora/src/lib.rs @@ -7,6 +7,8 @@ pub(crate) mod fmt; #[cfg(feature = "stm32wl")] pub mod stm32wl; +#[cfg(feature = "sx126x")] +pub mod sx126x; #[cfg(feature = "sx127x")] pub mod sx127x; diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs index 8d5d19531..3d52c1cc7 100644 --- a/embassy-lora/src/stm32wl/mod.rs +++ b/embassy-lora/src/stm32wl/mod.rs @@ -70,7 +70,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> { /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form /// the upcoming RX window start. async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result { - trace!("TX request: {}", config); + trace!("TX request: {:?}", config); self.switch.set_tx(); self.radio @@ -130,7 +130,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> { /// be able to hold a single LoRaWAN packet. async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> { assert!(buf.len() >= 255); - trace!("RX request: {}", config); + trace!("RX request: {:?}", config); self.switch.set_rx(); self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?; @@ -172,7 +172,11 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> { self.radio.read_buffer(ptr, &mut buf[..len as usize])?; self.radio.set_standby(StandbyClk::Rc)?; + #[cfg(feature = "defmt")] trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]); + + #[cfg(feature = "log")] + trace!("RX done: {:02x?}", &mut buf[..len as usize]); return Ok((len as usize, RxQuality::new(rssi, snr as i8))); } @@ -193,7 +197,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> { .clear_irq_status(irq_status) .expect("error clearing irq status"); - trace!("SUGHZ IRQ 0b{=u16:b}, {:?}", irq_status, status); + trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status); if irq_status == 0 { Poll::Pending @@ -256,10 +260,10 @@ impl From for RadioError { impl<'d, RS> Timings for SubGhzRadio<'d, RS> { fn get_rx_window_offset_ms(&self) -> i32 { - -500 + -3 } fn get_rx_window_duration_ms(&self) -> u32 { - 3000 + 1003 } } diff --git a/embassy-lora/src/sx126x/mod.rs b/embassy-lora/src/sx126x/mod.rs new file mode 100644 index 000000000..a9aeadfcb --- /dev/null +++ b/embassy-lora/src/sx126x/mod.rs @@ -0,0 +1,153 @@ +use core::future::Future; + +use defmt::Format; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::*; +use lorawan_device::async_device::radio::{PhyRxTx, RfConfig, RxQuality, TxConfig}; +use lorawan_device::async_device::Timings; + +mod sx126x_lora; +use sx126x_lora::LoRa; + +use self::sx126x_lora::mod_params::RadioError; + +/// Semtech Sx126x LoRa peripheral +pub struct Sx126xRadio +where + SPI: SpiBus + 'static, + CTRL: OutputPin + 'static, + WAIT: Wait + 'static, + BUS: Error + Format + 'static, +{ + pub lora: LoRa, +} + +impl Sx126xRadio +where + SPI: SpiBus + 'static, + CTRL: OutputPin + 'static, + WAIT: Wait + 'static, + BUS: Error + Format + 'static, +{ + pub async fn new( + spi: SPI, + cs: CTRL, + reset: CTRL, + antenna_rx: CTRL, + antenna_tx: CTRL, + dio1: WAIT, + busy: WAIT, + enable_public_network: bool, + ) -> Result> { + let mut lora = LoRa::new(spi, cs, reset, antenna_rx, antenna_tx, dio1, busy); + lora.init().await?; + lora.set_lora_modem(enable_public_network).await?; + Ok(Self { lora }) + } +} + +impl Timings for Sx126xRadio +where + SPI: SpiBus + 'static, + CTRL: OutputPin + 'static, + WAIT: Wait + 'static, + BUS: Error + Format + 'static, +{ + fn get_rx_window_offset_ms(&self) -> i32 { + -3 + } + fn get_rx_window_duration_ms(&self) -> u32 { + 1003 + } +} + +impl PhyRxTx for Sx126xRadio +where + SPI: SpiBus + 'static, + CTRL: OutputPin + 'static, + WAIT: Wait + 'static, + BUS: Error + Format + 'static, +{ + type PhyError = RadioError; + + type TxFuture<'m> = impl Future> + 'm + where + SPI: 'm, + CTRL: 'm, + WAIT: 'm, + BUS: 'm; + + fn tx<'m>(&'m mut self, config: TxConfig, buffer: &'m [u8]) -> Self::TxFuture<'m> { + trace!("TX START"); + async move { + self.lora + .set_tx_config( + config.pw, + config.rf.spreading_factor.into(), + config.rf.bandwidth.into(), + config.rf.coding_rate.into(), + 8, + false, + true, + false, + 0, + false, + ) + .await?; + self.lora.set_max_payload_length(buffer.len() as u8).await?; + self.lora.set_channel(config.rf.frequency).await?; + self.lora.send(buffer, 0xffffff).await?; + self.lora.process_irq(None, None, None).await?; + trace!("TX DONE"); + return Ok(0); + } + } + + type RxFuture<'m> = impl Future> + 'm + where + SPI: 'm, + CTRL: 'm, + WAIT: 'm, + BUS: 'm; + + fn rx<'m>(&'m mut self, config: RfConfig, receiving_buffer: &'m mut [u8]) -> Self::RxFuture<'m> { + trace!("RX START"); + async move { + self.lora + .set_rx_config( + config.spreading_factor.into(), + config.bandwidth.into(), + config.coding_rate.into(), + 8, + 4, + false, + 0u8, + true, + false, + 0, + true, + true, + ) + .await?; + self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?; + self.lora.set_channel(config.frequency).await?; + self.lora.rx(90 * 1000).await?; + let mut received_len = 0u8; + self.lora + .process_irq(Some(receiving_buffer), Some(&mut received_len), None) + .await?; + trace!("RX DONE"); + + let packet_status = self.lora.get_latest_packet_status(); + let mut rssi = 0i16; + let mut snr = 0i8; + if packet_status.is_some() { + rssi = packet_status.unwrap().rssi as i16; + snr = packet_status.unwrap().snr; + } + + Ok((received_len as usize, RxQuality::new(rssi, snr))) + } + } +} diff --git a/embassy-lora/src/sx126x/sx126x_lora/board_specific.rs b/embassy-lora/src/sx126x/sx126x_lora/board_specific.rs new file mode 100644 index 000000000..a7b9e1486 --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/board_specific.rs @@ -0,0 +1,256 @@ +use embassy_time::{Duration, Timer}; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiBus; + +use super::mod_params::RadioError::*; +use super::mod_params::*; +use super::LoRa; + +// Defines the time required for the TCXO to wakeup [ms]. +const BRD_TCXO_WAKEUP_TIME: u32 = 10; + +// Provides board-specific functionality for Semtech SX126x-based boards. + +impl LoRa +where + SPI: SpiBus, + CTRL: OutputPin, + WAIT: Wait, +{ + // De-initialize the radio I/Os pins interface. Useful when going into MCU low power modes. + pub(super) async fn brd_io_deinit(&mut self) -> Result<(), RadioError> { + Ok(()) // no operation currently + } + + // Initialize the TCXO power pin + pub(super) async fn brd_io_tcxo_init(&mut self) -> Result<(), RadioError> { + let timeout = self.brd_get_board_tcxo_wakeup_time() << 6; + self.sub_set_dio3_as_tcxo_ctrl(TcxoCtrlVoltage::Ctrl1V7, timeout) + .await?; + Ok(()) + } + + // Initialize RF switch control pins + pub(super) async fn brd_io_rf_switch_init(&mut self) -> Result<(), RadioError> { + self.sub_set_dio2_as_rf_switch_ctrl(true).await?; + Ok(()) + } + + // Initialize the radio debug pins + pub(super) async fn brd_io_dbg_init(&mut self) -> Result<(), RadioError> { + Ok(()) // no operation currently + } + + // Hardware reset of the radio + pub(super) async fn brd_reset(&mut self) -> Result<(), RadioError> { + Timer::after(Duration::from_millis(10)).await; + self.reset.set_low().map_err(|_| Reset)?; + Timer::after(Duration::from_millis(20)).await; + self.reset.set_high().map_err(|_| Reset)?; + Timer::after(Duration::from_millis(10)).await; + Ok(()) + } + + // Wait while the busy pin is high + pub(super) async fn brd_wait_on_busy(&mut self) -> Result<(), RadioError> { + self.busy.wait_for_low().await.map_err(|_| Busy)?; + Ok(()) + } + + // Wake up the radio + pub(super) async fn brd_wakeup(&mut self) -> Result<(), RadioError> { + self.cs.set_low().map_err(|_| CS)?; + self.spi.write(&[OpCode::GetStatus.value()]).await.map_err(SPI)?; + self.spi.write(&[0x00]).await.map_err(SPI)?; + self.cs.set_high().map_err(|_| CS)?; + + self.brd_wait_on_busy().await?; + self.brd_set_operating_mode(RadioMode::StandbyRC); + Ok(()) + } + + // Send a command that writes data to the radio + pub(super) async fn brd_write_command(&mut self, op_code: OpCode, buffer: &[u8]) -> Result<(), RadioError> { + self.sub_check_device_ready().await?; + + self.cs.set_low().map_err(|_| CS)?; + self.spi.write(&[op_code.value()]).await.map_err(SPI)?; + self.spi.write(buffer).await.map_err(SPI)?; + self.cs.set_high().map_err(|_| CS)?; + + if op_code != OpCode::SetSleep { + self.brd_wait_on_busy().await?; + } + Ok(()) + } + + // Send a command that reads data from the radio, filling the provided buffer and returning a status + pub(super) async fn brd_read_command(&mut self, op_code: OpCode, buffer: &mut [u8]) -> Result> { + let mut status = [0u8]; + let mut input = [0u8]; + + self.sub_check_device_ready().await?; + + self.cs.set_low().map_err(|_| CS)?; + self.spi.write(&[op_code.value()]).await.map_err(SPI)?; + self.spi.transfer(&mut status, &[0x00]).await.map_err(SPI)?; + for i in 0..buffer.len() { + self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?; + buffer[i] = input[0]; + } + self.cs.set_high().map_err(|_| CS)?; + + self.brd_wait_on_busy().await?; + + Ok(status[0]) + } + + // Write one or more bytes of data to the radio memory + pub(super) async fn brd_write_registers( + &mut self, + start_register: Register, + buffer: &[u8], + ) -> Result<(), RadioError> { + self.sub_check_device_ready().await?; + + self.cs.set_low().map_err(|_| CS)?; + self.spi.write(&[OpCode::WriteRegister.value()]).await.map_err(SPI)?; + self.spi + .write(&[ + ((start_register.addr() & 0xFF00) >> 8) as u8, + (start_register.addr() & 0x00FF) as u8, + ]) + .await + .map_err(SPI)?; + self.spi.write(buffer).await.map_err(SPI)?; + self.cs.set_high().map_err(|_| CS)?; + + self.brd_wait_on_busy().await?; + Ok(()) + } + + // Read one or more bytes of data from the radio memory + pub(super) async fn brd_read_registers( + &mut self, + start_register: Register, + buffer: &mut [u8], + ) -> Result<(), RadioError> { + let mut input = [0u8]; + + self.sub_check_device_ready().await?; + + self.cs.set_low().map_err(|_| CS)?; + self.spi.write(&[OpCode::ReadRegister.value()]).await.map_err(SPI)?; + self.spi + .write(&[ + ((start_register.addr() & 0xFF00) >> 8) as u8, + (start_register.addr() & 0x00FF) as u8, + 0x00u8, + ]) + .await + .map_err(SPI)?; + for i in 0..buffer.len() { + self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?; + buffer[i] = input[0]; + } + self.cs.set_high().map_err(|_| CS)?; + + self.brd_wait_on_busy().await?; + Ok(()) + } + + // Write data to the buffer holding the payload in the radio + pub(super) async fn brd_write_buffer(&mut self, offset: u8, buffer: &[u8]) -> Result<(), RadioError> { + self.sub_check_device_ready().await?; + + self.cs.set_low().map_err(|_| CS)?; + self.spi.write(&[OpCode::WriteBuffer.value()]).await.map_err(SPI)?; + self.spi.write(&[offset]).await.map_err(SPI)?; + self.spi.write(buffer).await.map_err(SPI)?; + self.cs.set_high().map_err(|_| CS)?; + + self.brd_wait_on_busy().await?; + Ok(()) + } + + // Read data from the buffer holding the payload in the radio + pub(super) async fn brd_read_buffer(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), RadioError> { + let mut input = [0u8]; + + self.sub_check_device_ready().await?; + + self.cs.set_low().map_err(|_| CS)?; + self.spi.write(&[OpCode::ReadBuffer.value()]).await.map_err(SPI)?; + self.spi.write(&[offset]).await.map_err(SPI)?; + self.spi.write(&[0x00]).await.map_err(SPI)?; + for i in 0..buffer.len() { + self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?; + buffer[i] = input[0]; + } + self.cs.set_high().map_err(|_| CS)?; + + self.brd_wait_on_busy().await?; + Ok(()) + } + + // Set the radio output power + pub(super) async fn brd_set_rf_tx_power(&mut self, power: i8) -> Result<(), RadioError> { + self.sub_set_tx_params(power, RampTime::Ramp40Us).await?; + Ok(()) + } + + // Get the radio type + pub(super) fn brd_get_radio_type(&mut self) -> RadioType { + RadioType::SX1262 + } + + // Quiesce the antenna(s). + pub(super) fn brd_ant_sleep(&mut self) -> Result<(), RadioError> { + self.antenna_tx.set_low().map_err(|_| AntTx)?; + self.antenna_rx.set_low().map_err(|_| AntRx)?; + Ok(()) + } + + // Prepare the antenna(s) for a receive operation + pub(super) fn brd_ant_set_rx(&mut self) -> Result<(), RadioError> { + self.antenna_tx.set_low().map_err(|_| AntTx)?; + self.antenna_rx.set_high().map_err(|_| AntRx)?; + Ok(()) + } + + // Prepare the antenna(s) for a send operation + pub(super) fn brd_ant_set_tx(&mut self) -> Result<(), RadioError> { + self.antenna_rx.set_low().map_err(|_| AntRx)?; + self.antenna_tx.set_high().map_err(|_| AntTx)?; + Ok(()) + } + + // Check if the given RF frequency is supported by the hardware + pub(super) async fn brd_check_rf_frequency(&mut self, _frequency: u32) -> Result> { + Ok(true) + } + + // Get the duration required for the TCXO to wakeup [ms]. + pub(super) fn brd_get_board_tcxo_wakeup_time(&mut self) -> u32 { + BRD_TCXO_WAKEUP_TIME + } + + /* Get current state of the DIO1 pin - not currently needed if waiting on DIO1 instead of using an IRQ process + pub(super) async fn brd_get_dio1_pin_state( + &mut self, + ) -> Result> { + Ok(0) + } + */ + + // Get the current radio operatiing mode + pub(super) fn brd_get_operating_mode(&mut self) -> RadioMode { + self.operating_mode + } + + // Set/Update the current radio operating mode This function is only required to reflect the current radio operating mode when processing interrupts. + pub(super) fn brd_set_operating_mode(&mut self, mode: RadioMode) { + self.operating_mode = mode; + } +} diff --git a/embassy-lora/src/sx126x/sx126x_lora/mod.rs b/embassy-lora/src/sx126x/sx126x_lora/mod.rs new file mode 100644 index 000000000..280f26d51 --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/mod.rs @@ -0,0 +1,732 @@ +#![allow(dead_code)] + +use embassy_time::{Duration, Timer}; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiBus; + +mod board_specific; +pub mod mod_params; +mod subroutine; + +use mod_params::RadioError::*; +use mod_params::*; + +// Syncwords for public and private networks +const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444; +const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424; + +// Maximum number of registers that can be added to the retention list +const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4; + +// Possible LoRa bandwidths +const LORA_BANDWIDTHS: [Bandwidth; 3] = [Bandwidth::_125KHz, Bandwidth::_250KHz, Bandwidth::_500KHz]; + +// Radio complete wakeup time with margin for temperature compensation [ms] +const RADIO_WAKEUP_TIME: u32 = 3; + +/// Provides high-level access to Semtech SX126x-based boards +pub struct LoRa { + spi: SPI, + cs: CTRL, + reset: CTRL, + antenna_rx: CTRL, + antenna_tx: CTRL, + dio1: WAIT, + busy: WAIT, + operating_mode: RadioMode, + rx_continuous: bool, + max_payload_length: u8, + modulation_params: Option, + packet_type: PacketType, + packet_params: Option, + packet_status: Option, + image_calibrated: bool, + frequency_error: u32, +} + +impl LoRa +where + SPI: SpiBus, + CTRL: OutputPin, + WAIT: Wait, +{ + /// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time () + pub fn new(spi: SPI, cs: CTRL, reset: CTRL, antenna_rx: CTRL, antenna_tx: CTRL, dio1: WAIT, busy: WAIT) -> Self { + Self { + spi, + cs, + reset, + antenna_rx, + antenna_tx, + dio1, + busy, + operating_mode: RadioMode::Sleep, + rx_continuous: false, + max_payload_length: 0xFFu8, + modulation_params: None, + packet_type: PacketType::LoRa, + packet_params: None, + packet_status: None, + image_calibrated: false, + frequency_error: 0u32, // where is volatile FrequencyError modified ??? + } + } + + /// Initialize the radio + pub async fn init(&mut self) -> Result<(), RadioError> { + self.sub_init().await?; + self.sub_set_standby(StandbyMode::RC).await?; + self.sub_set_regulator_mode(RegulatorMode::UseDCDC).await?; + self.sub_set_buffer_base_address(0x00u8, 0x00u8).await?; + self.sub_set_tx_params(0i8, RampTime::Ramp200Us).await?; + self.sub_set_dio_irq_params( + IrqMask::All.value(), + IrqMask::All.value(), + IrqMask::None.value(), + IrqMask::None.value(), + ) + .await?; + self.add_register_to_retention_list(Register::RxGain.addr()).await?; + self.add_register_to_retention_list(Register::TxModulation.addr()) + .await?; + Ok(()) + } + + /// Return current radio state + pub fn get_status(&mut self) -> RadioState { + match self.brd_get_operating_mode() { + RadioMode::Transmit => RadioState::TxRunning, + RadioMode::Receive => RadioState::RxRunning, + RadioMode::ChannelActivityDetection => RadioState::ChannelActivityDetecting, + _ => RadioState::Idle, + } + } + + /// Configure the radio for LoRa (FSK support should be provided in a separate driver, if desired) + pub async fn set_lora_modem(&mut self, enable_public_network: bool) -> Result<(), RadioError> { + self.sub_set_packet_type(PacketType::LoRa).await?; + if enable_public_network { + self.brd_write_registers( + Register::LoRaSyncword, + &[ + ((LORA_MAC_PUBLIC_SYNCWORD >> 8) & 0xFF) as u8, + (LORA_MAC_PUBLIC_SYNCWORD & 0xFF) as u8, + ], + ) + .await?; + } else { + self.brd_write_registers( + Register::LoRaSyncword, + &[ + ((LORA_MAC_PRIVATE_SYNCWORD >> 8) & 0xFF) as u8, + (LORA_MAC_PRIVATE_SYNCWORD & 0xFF) as u8, + ], + ) + .await?; + } + + Ok(()) + } + + /// Sets the channel frequency + pub async fn set_channel(&mut self, frequency: u32) -> Result<(), RadioError> { + self.sub_set_rf_frequency(frequency).await?; + Ok(()) + } + + /* Checks if the channel is free for the given time. This is currently not implemented until a substitute + for switching to the FSK modem is found. + + pub async fn is_channel_free(&mut self, frequency: u32, rxBandwidth: u32, rssiThresh: i16, maxCarrierSenseTime: u32) -> bool; + */ + + /// Generate a 32 bit random value based on the RSSI readings, after disabling all interrupts. Ensure set_lora_modem() is called befrorehand. + /// After calling this function either set_rx_config() or set_tx_config() must be called. + pub async fn get_random_value(&mut self) -> Result> { + self.sub_set_dio_irq_params( + IrqMask::None.value(), + IrqMask::None.value(), + IrqMask::None.value(), + IrqMask::None.value(), + ) + .await?; + + let result = self.sub_get_random().await?; + Ok(result) + } + + /// Set the reception parameters for the LoRa modem (only). Ensure set_lora_modem() is called befrorehand. + /// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol] + /// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved] + /// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] + /// preamble_length length in symbols (the hardware adds 4 more symbols) + /// symb_timeout RxSingle timeout value in symbols + /// fixed_len fixed length packets [0: variable, 1: fixed] + /// payload_len payload length when fixed length is used + /// crc_on [0: OFF, 1: ON] + /// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON] + /// hop_period number of symbols between each hop + /// iq_inverted invert IQ signals [0: not inverted, 1: inverted] + /// rx_continuous reception mode [false: single mode, true: continuous mode] + pub async fn set_rx_config( + &mut self, + spreading_factor: SpreadingFactor, + bandwidth: Bandwidth, + coding_rate: CodingRate, + preamble_length: u16, + symb_timeout: u16, + fixed_len: bool, + payload_len: u8, + crc_on: bool, + _freq_hop_on: bool, + _hop_period: u8, + iq_inverted: bool, + rx_continuous: bool, + ) -> Result<(), RadioError> { + let mut symb_timeout_final = symb_timeout; + + self.rx_continuous = rx_continuous; + if self.rx_continuous { + symb_timeout_final = 0; + } + if fixed_len { + self.max_payload_length = payload_len; + } else { + self.max_payload_length = 0xFFu8; + } + + self.sub_set_stop_rx_timer_on_preamble_detect(false).await?; + + let mut low_data_rate_optimize = 0x00u8; + if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12)) + && (bandwidth == Bandwidth::_125KHz)) + || ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz)) + { + low_data_rate_optimize = 0x01u8; + } + + let modulation_params = ModulationParams { + spreading_factor: spreading_factor, + bandwidth: bandwidth, + coding_rate: coding_rate, + low_data_rate_optimize: low_data_rate_optimize, + }; + + let mut preamble_length_final = preamble_length; + if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6)) + && (preamble_length < 12) + { + preamble_length_final = 12; + } + + let packet_params = PacketParams { + preamble_length: preamble_length_final, + implicit_header: fixed_len, + payload_length: self.max_payload_length, + crc_on: crc_on, + iq_inverted: iq_inverted, + }; + + self.modulation_params = Some(modulation_params); + self.packet_params = Some(packet_params); + + self.standby().await?; + self.sub_set_modulation_params().await?; + self.sub_set_packet_params().await?; + self.sub_set_lora_symb_num_timeout(symb_timeout_final).await?; + + // Optimize the Inverted IQ Operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4) + let mut iq_polarity = [0x00u8]; + self.brd_read_registers(Register::IQPolarity, &mut iq_polarity).await?; + if iq_inverted { + self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] & (!(1 << 2))]) + .await?; + } else { + self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] | (1 << 2)]) + .await?; + } + Ok(()) + } + + /// Set the transmission parameters for the LoRa modem (only). + /// power output power [dBm] + /// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol] + /// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved] + /// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] + /// preamble_length length in symbols (the hardware adds 4 more symbols) + /// fixed_len fixed length packets [0: variable, 1: fixed] + /// crc_on [0: OFF, 1: ON] + /// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON] + /// hop_period number of symbols between each hop + /// iq_inverted invert IQ signals [0: not inverted, 1: inverted] + pub async fn set_tx_config( + &mut self, + power: i8, + spreading_factor: SpreadingFactor, + bandwidth: Bandwidth, + coding_rate: CodingRate, + preamble_length: u16, + fixed_len: bool, + crc_on: bool, + _freq_hop_on: bool, + _hop_period: u8, + iq_inverted: bool, + ) -> Result<(), RadioError> { + let mut low_data_rate_optimize = 0x00u8; + if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12)) + && (bandwidth == Bandwidth::_125KHz)) + || ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz)) + { + low_data_rate_optimize = 0x01u8; + } + + let modulation_params = ModulationParams { + spreading_factor: spreading_factor, + bandwidth: bandwidth, + coding_rate: coding_rate, + low_data_rate_optimize: low_data_rate_optimize, + }; + + let mut preamble_length_final = preamble_length; + if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6)) + && (preamble_length < 12) + { + preamble_length_final = 12; + } + + let packet_params = PacketParams { + preamble_length: preamble_length_final, + implicit_header: fixed_len, + payload_length: self.max_payload_length, + crc_on: crc_on, + iq_inverted: iq_inverted, + }; + + self.modulation_params = Some(modulation_params); + self.packet_params = Some(packet_params); + + self.standby().await?; + self.sub_set_modulation_params().await?; + self.sub_set_packet_params().await?; + + // Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1) + + let mut tx_modulation = [0x00u8]; + self.brd_read_registers(Register::TxModulation, &mut tx_modulation) + .await?; + if bandwidth == Bandwidth::_500KHz { + self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] & (!(1 << 2))]) + .await?; + } else { + self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] | (1 << 2)]) + .await?; + } + + self.brd_set_rf_tx_power(power).await?; + Ok(()) + } + + /// Check if the given RF frequency is supported by the hardware [true: supported, false: unsupported] + pub async fn check_rf_frequency(&mut self, frequency: u32) -> Result> { + Ok(self.brd_check_rf_frequency(frequency).await?) + } + + /// Computes the packet time on air in ms for the given payload for a LoRa modem (can only be called once set_rx_config or set_tx_config have been called) + /// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol] + /// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved] + /// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] + /// preamble_length length in symbols (the hardware adds 4 more symbols) + /// fixed_len fixed length packets [0: variable, 1: fixed] + /// payload_len sets payload length when fixed length is used + /// crc_on [0: OFF, 1: ON] + pub fn get_time_on_air( + &mut self, + spreading_factor: SpreadingFactor, + bandwidth: Bandwidth, + coding_rate: CodingRate, + preamble_length: u16, + fixed_len: bool, + payload_len: u8, + crc_on: bool, + ) -> Result> { + let numerator = 1000 + * Self::get_lora_time_on_air_numerator( + spreading_factor, + bandwidth, + coding_rate, + preamble_length, + fixed_len, + payload_len, + crc_on, + ); + let denominator = bandwidth.value_in_hz(); + if denominator == 0 { + Err(RadioError::InvalidBandwidth) + } else { + Ok((numerator + denominator - 1) / denominator) + } + } + + /// Send the buffer of the given size. Prepares the packet to be sent and sets the radio in transmission [timeout in ms] + pub async fn send(&mut self, buffer: &[u8], timeout: u32) -> Result<(), RadioError> { + if self.packet_params.is_some() { + self.sub_set_dio_irq_params( + IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(), + IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(), + IrqMask::None.value(), + IrqMask::None.value(), + ) + .await?; + + let mut packet_params = self.packet_params.as_mut().unwrap(); + packet_params.payload_length = buffer.len() as u8; + self.sub_set_packet_params().await?; + self.sub_send_payload(buffer, timeout).await?; + Ok(()) + } else { + Err(RadioError::PacketParamsMissing) + } + } + + /// Set the radio in sleep mode + pub async fn sleep(&mut self) -> Result<(), RadioError> { + self.sub_set_sleep(SleepParams { + wakeup_rtc: false, + reset: false, + warm_start: true, + }) + .await?; + Timer::after(Duration::from_millis(2)).await; + Ok(()) + } + + /// Set the radio in standby mode + pub async fn standby(&mut self) -> Result<(), RadioError> { + self.sub_set_standby(StandbyMode::RC).await?; + Ok(()) + } + + /// Set the radio in reception mode for the given duration [0: continuous, others: timeout (ms)] + pub async fn rx(&mut self, timeout: u32) -> Result<(), RadioError> { + self.sub_set_dio_irq_params( + IrqMask::All.value(), + IrqMask::All.value(), + IrqMask::None.value(), + IrqMask::None.value(), + ) + .await?; + + if self.rx_continuous { + self.sub_set_rx(0xFFFFFF).await?; + } else { + self.sub_set_rx(timeout << 6).await?; + } + + Ok(()) + } + + /// Start a Channel Activity Detection + pub async fn start_cad(&mut self) -> Result<(), RadioError> { + self.sub_set_dio_irq_params( + IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(), + IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(), + IrqMask::None.value(), + IrqMask::None.value(), + ) + .await?; + self.sub_set_cad().await?; + Ok(()) + } + + /// Sets the radio in continuous wave transmission mode + /// frequency channel RF frequency + /// power output power [dBm] + /// timeout transmission mode timeout [s] + pub async fn set_tx_continuous_wave( + &mut self, + frequency: u32, + power: i8, + _timeout: u16, + ) -> Result<(), RadioError> { + self.sub_set_rf_frequency(frequency).await?; + self.brd_set_rf_tx_power(power).await?; + self.sub_set_tx_continuous_wave().await?; + + Ok(()) + } + + /// Read the current RSSI value for the LoRa modem (only) [dBm] + pub async fn get_rssi(&mut self) -> Result> { + let value = self.sub_get_rssi_inst().await?; + Ok(value as i16) + } + + /// Write one or more radio registers with a buffer of a given size, starting at the first register address + pub async fn write_registers_from_buffer( + &mut self, + start_register: Register, + buffer: &[u8], + ) -> Result<(), RadioError> { + self.brd_write_registers(start_register, buffer).await?; + Ok(()) + } + + /// Read one or more radio registers into a buffer of a given size, starting at the first register address + pub async fn read_registers_into_buffer( + &mut self, + start_register: Register, + buffer: &mut [u8], + ) -> Result<(), RadioError> { + self.brd_read_registers(start_register, buffer).await?; + Ok(()) + } + + /// Set the maximum payload length (in bytes) for a LoRa modem (only). + pub async fn set_max_payload_length(&mut self, max: u8) -> Result<(), RadioError> { + if self.packet_params.is_some() { + let packet_params = self.packet_params.as_mut().unwrap(); + self.max_payload_length = max; + packet_params.payload_length = max; + self.sub_set_packet_params().await?; + Ok(()) + } else { + Err(RadioError::PacketParamsMissing) + } + } + + /// Get the time required for the board plus radio to get out of sleep [ms] + pub fn get_wakeup_time(&mut self) -> u32 { + self.brd_get_board_tcxo_wakeup_time() + RADIO_WAKEUP_TIME + } + + /// Process the radio irq + pub async fn process_irq( + &mut self, + receiving_buffer: Option<&mut [u8]>, + received_len: Option<&mut u8>, + cad_activity_detected: Option<&mut bool>, + ) -> Result<(), RadioError> { + loop { + trace!("process_irq loop entered"); + + let de = self.sub_get_device_errors().await?; + trace!("device_errors: rc_64khz_calibration = {}, rc_13mhz_calibration = {}, pll_calibration = {}, adc_calibration = {}, image_calibration = {}, xosc_start = {}, pll_lock = {}, pa_ramp = {}", + de.rc_64khz_calibration, de.rc_13mhz_calibration, de.pll_calibration, de.adc_calibration, de.image_calibration, de.xosc_start, de.pll_lock, de.pa_ramp); + let st = self.sub_get_status().await?; + trace!( + "radio status: cmd_status: {:x}, chip_mode: {:x}", + st.cmd_status, + st.chip_mode + ); + + self.dio1.wait_for_high().await.map_err(|_| DIO1)?; + let operating_mode = self.brd_get_operating_mode(); + let irq_flags = self.sub_get_irq_status().await?; + self.sub_clear_irq_status(irq_flags).await?; + trace!("process_irq DIO1 satisfied: irq_flags = {:x}", irq_flags); + + // check for errors and unexpected interrupt masks (based on operation mode) + if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() { + if !self.rx_continuous { + self.brd_set_operating_mode(RadioMode::StandbyRC); + } + return Err(RadioError::HeaderError); + } else if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() { + if operating_mode == RadioMode::Receive { + if !self.rx_continuous { + self.brd_set_operating_mode(RadioMode::StandbyRC); + } + return Err(RadioError::CRCErrorOnReceive); + } else { + return Err(RadioError::CRCErrorUnexpected); + } + } else if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() { + if operating_mode == RadioMode::Transmit { + self.brd_set_operating_mode(RadioMode::StandbyRC); + return Err(RadioError::TransmitTimeout); + } else if operating_mode == RadioMode::Receive { + self.brd_set_operating_mode(RadioMode::StandbyRC); + return Err(RadioError::ReceiveTimeout); + } else { + return Err(RadioError::TimeoutUnexpected); + } + } else if ((irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value()) + && (operating_mode != RadioMode::Transmit) + { + return Err(RadioError::TransmitDoneUnexpected); + } else if ((irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value()) + && (operating_mode != RadioMode::Receive) + { + return Err(RadioError::ReceiveDoneUnexpected); + } else if (((irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value()) + || ((irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value())) + && (operating_mode != RadioMode::ChannelActivityDetection) + { + return Err(RadioError::CADUnexpected); + } + + if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() { + trace!("HeaderValid"); + } else if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() { + trace!("PreambleDetected"); + } else if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() { + trace!("SyncwordValid"); + } + + // handle completions + if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() { + self.brd_set_operating_mode(RadioMode::StandbyRC); + return Ok(()); + } else if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() { + if !self.rx_continuous { + self.brd_set_operating_mode(RadioMode::StandbyRC); + + // implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3) + self.brd_write_registers(Register::RTCCtrl, &[0x00]).await?; + let mut evt_clr = [0x00u8]; + self.brd_read_registers(Register::EvtClr, &mut evt_clr).await?; + evt_clr[0] |= 1 << 1; + self.brd_write_registers(Register::EvtClr, &evt_clr).await?; + } + + if receiving_buffer.is_some() && received_len.is_some() { + *(received_len.unwrap()) = self.sub_get_payload(receiving_buffer.unwrap()).await?; + } + self.packet_status = self.sub_get_packet_status().await?.into(); + return Ok(()); + } else if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() { + if cad_activity_detected.is_some() { + *(cad_activity_detected.unwrap()) = + (irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value(); + } + self.brd_set_operating_mode(RadioMode::StandbyRC); + return Ok(()); + } + + // if DIO1 was driven high for reasons other than an error or operation completion (currently, PreambleDetected, SyncwordValid, and HeaderValid + // are in that category), loop to wait again + } + } + + // SX126x-specific functions + + /// Set the radio in reception mode with Max LNA gain for the given time (SX126x radios only) [0: continuous, others timeout in ms] + pub async fn set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError> { + self.sub_set_dio_irq_params( + IrqMask::All.value(), + IrqMask::All.value(), + IrqMask::None.value(), + IrqMask::None.value(), + ) + .await?; + + if self.rx_continuous { + self.sub_set_rx_boosted(0xFFFFFF).await?; // Rx continuous + } else { + self.sub_set_rx_boosted(timeout << 6).await?; + } + + Ok(()) + } + + /// Set the Rx duty cycle management parameters (SX126x radios only) + /// rx_time structure describing reception timeout value + /// sleep_time structure describing sleep timeout value + pub async fn set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError> { + self.sub_set_rx_duty_cycle(rx_time, sleep_time).await?; + Ok(()) + } + + pub fn get_latest_packet_status(&mut self) -> Option { + self.packet_status + } + + // Utilities + + async fn add_register_to_retention_list(&mut self, register_address: u16) -> Result<(), RadioError> { + let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize]; + + // Read the address and registers already added to the list + self.brd_read_registers(Register::RetentionList, &mut buffer).await?; + + let number_of_registers = buffer[0]; + for i in 0..number_of_registers { + if register_address + == ((buffer[(1 + (2 * i)) as usize] as u16) << 8) | (buffer[(2 + (2 * i)) as usize] as u16) + { + return Ok(()); // register already in list + } + } + + if number_of_registers < MAX_NUMBER_REGS_IN_RETENTION { + buffer[0] += 1; // increment number of registers + + buffer[(1 + (2 * number_of_registers)) as usize] = ((register_address >> 8) & 0xFF) as u8; + buffer[(2 + (2 * number_of_registers)) as usize] = (register_address & 0xFF) as u8; + self.brd_write_registers(Register::RetentionList, &buffer).await?; + + Ok(()) + } else { + Err(RadioError::RetentionListExceeded) + } + } + + fn get_lora_time_on_air_numerator( + spreading_factor: SpreadingFactor, + bandwidth: Bandwidth, + coding_rate: CodingRate, + preamble_length: u16, + fixed_len: bool, + payload_len: u8, + crc_on: bool, + ) -> u32 { + let cell_denominator; + let cr_denominator = (coding_rate.value() as i32) + 4; + + // Ensure that the preamble length is at least 12 symbols when using SF5 or SF6 + let mut preamble_length_final = preamble_length; + if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6)) + && (preamble_length < 12) + { + preamble_length_final = 12; + } + + let mut low_data_rate_optimize = false; + if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12)) + && (bandwidth == Bandwidth::_125KHz)) + || ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz)) + { + low_data_rate_optimize = true; + } + + let mut cell_numerator = ((payload_len as i32) << 3) + (if crc_on { 16 } else { 0 }) + - (4 * spreading_factor.value() as i32) + + (if fixed_len { 0 } else { 20 }); + + if spreading_factor.value() <= 6 { + cell_denominator = 4 * (spreading_factor.value() as i32); + } else { + cell_numerator += 8; + if low_data_rate_optimize { + cell_denominator = 4 * ((spreading_factor.value() as i32) - 2); + } else { + cell_denominator = 4 * (spreading_factor.value() as i32); + } + } + + if cell_numerator < 0 { + cell_numerator = 0; + } + + let mut intermediate: i32 = (((cell_numerator + cell_denominator - 1) / cell_denominator) * cr_denominator) + + (preamble_length_final as i32) + + 12; + + if spreading_factor.value() <= 6 { + intermediate = intermediate + 2; + } + + (((4 * intermediate) + 1) * (1 << (spreading_factor.value() - 2))) as u32 + } +} diff --git a/embassy-lora/src/sx126x/sx126x_lora/mod_params.rs b/embassy-lora/src/sx126x/sx126x_lora/mod_params.rs new file mode 100644 index 000000000..e270b2a09 --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/mod_params.rs @@ -0,0 +1,469 @@ +use core::fmt::Debug; + +use lorawan_device::async_device::radio as device; + +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RadioError { + SPI(BUS), + CS, + Reset, + AntRx, + AntTx, + Busy, + DIO1, + PayloadSizeMismatch(usize, usize), + RetentionListExceeded, + InvalidBandwidth, + ModulationParamsMissing, + PacketParamsMissing, + HeaderError, + CRCErrorUnexpected, + CRCErrorOnReceive, + TransmitTimeout, + ReceiveTimeout, + TimeoutUnexpected, + TransmitDoneUnexpected, + ReceiveDoneUnexpected, + CADUnexpected, +} + +pub struct RadioSystemError { + pub rc_64khz_calibration: bool, + pub rc_13mhz_calibration: bool, + pub pll_calibration: bool, + pub adc_calibration: bool, + pub image_calibration: bool, + pub xosc_start: bool, + pub pll_lock: bool, + pub pa_ramp: bool, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum PacketType { + GFSK = 0x00, + LoRa = 0x01, + None = 0x0F, +} + +impl PacketType { + pub const fn value(self) -> u8 { + self as u8 + } + pub fn to_enum(value: u8) -> Self { + if value == 0x00 { + PacketType::GFSK + } else if value == 0x01 { + PacketType::LoRa + } else { + PacketType::None + } + } +} + +#[derive(Clone, Copy)] +pub struct PacketStatus { + pub rssi: i8, + pub snr: i8, + pub signal_rssi: i8, + pub freq_error: u32, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum RadioType { + SX1261, + SX1262, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum RadioMode { + Sleep = 0x00, // sleep mode + StandbyRC = 0x01, // standby mode with RC oscillator + StandbyXOSC = 0x02, // standby mode with XOSC oscillator + FrequencySynthesis = 0x03, // frequency synthesis mode + Transmit = 0x04, // transmit mode + Receive = 0x05, // receive mode + ReceiveDutyCycle = 0x06, // receive duty cycle mode + ChannelActivityDetection = 0x07, // channel activity detection mode +} + +impl RadioMode { + /// Returns the value of the mode. + pub const fn value(self) -> u8 { + self as u8 + } + pub fn to_enum(value: u8) -> Self { + if value == 0x00 { + RadioMode::Sleep + } else if value == 0x01 { + RadioMode::StandbyRC + } else if value == 0x02 { + RadioMode::StandbyXOSC + } else if value == 0x03 { + RadioMode::FrequencySynthesis + } else if value == 0x04 { + RadioMode::Transmit + } else if value == 0x05 { + RadioMode::Receive + } else if value == 0x06 { + RadioMode::ReceiveDutyCycle + } else if value == 0x07 { + RadioMode::ChannelActivityDetection + } else { + RadioMode::Sleep + } + } +} + +pub enum RadioState { + Idle = 0x00, + RxRunning = 0x01, + TxRunning = 0x02, + ChannelActivityDetecting = 0x03, +} + +impl RadioState { + /// Returns the value of the state. + pub fn value(self) -> u8 { + self as u8 + } +} + +pub struct RadioStatus { + pub cmd_status: u8, + pub chip_mode: u8, +} + +impl RadioStatus { + pub fn value(self) -> u8 { + (self.chip_mode << 4) | (self.cmd_status << 1) + } +} + +#[derive(Clone, Copy)] +pub enum IrqMask { + None = 0x0000, + TxDone = 0x0001, + RxDone = 0x0002, + PreambleDetected = 0x0004, + SyncwordValid = 0x0008, + HeaderValid = 0x0010, + HeaderError = 0x0020, + CRCError = 0x0040, + CADDone = 0x0080, + CADActivityDetected = 0x0100, + RxTxTimeout = 0x0200, + All = 0xFFFF, +} + +impl IrqMask { + pub fn value(self) -> u16 { + self as u16 + } +} + +#[derive(Clone, Copy)] +pub enum Register { + PacketParams = 0x0704, // packet configuration + PayloadLength = 0x0702, // payload size + SynchTimeout = 0x0706, // recalculated number of symbols + Syncword = 0x06C0, // Syncword values + LoRaSyncword = 0x0740, // LoRa Syncword value + GeneratedRandomNumber = 0x0819, //32-bit generated random number + AnaLNA = 0x08E2, // disable the LNA + AnaMixer = 0x08E5, // disable the mixer + RxGain = 0x08AC, // RX gain (0x94: power saving, 0x96: rx boosted) + XTATrim = 0x0911, // device internal trimming capacitor + OCP = 0x08E7, // over current protection max value + RetentionList = 0x029F, // retention list + IQPolarity = 0x0736, // optimize the inverted IQ operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4) + TxModulation = 0x0889, // modulation quality with 500 kHz LoRa Bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1) + TxClampCfg = 0x08D8, // better resistance to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2) + RTCCtrl = 0x0902, // RTC control + EvtClr = 0x0944, // event clear +} + +impl Register { + pub fn addr(self) -> u16 { + self as u16 + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum OpCode { + GetStatus = 0xC0, + WriteRegister = 0x0D, + ReadRegister = 0x1D, + WriteBuffer = 0x0E, + ReadBuffer = 0x1E, + SetSleep = 0x84, + SetStandby = 0x80, + SetFS = 0xC1, + SetTx = 0x83, + SetRx = 0x82, + SetRxDutyCycle = 0x94, + SetCAD = 0xC5, + SetTxContinuousWave = 0xD1, + SetTxContinuousPremable = 0xD2, + SetPacketType = 0x8A, + GetPacketType = 0x11, + SetRFFrequency = 0x86, + SetTxParams = 0x8E, + SetPAConfig = 0x95, + SetCADParams = 0x88, + SetBufferBaseAddress = 0x8F, + SetModulationParams = 0x8B, + SetPacketParams = 0x8C, + GetRxBufferStatus = 0x13, + GetPacketStatus = 0x14, + GetRSSIInst = 0x15, + GetStats = 0x10, + ResetStats = 0x00, + CfgDIOIrq = 0x08, + GetIrqStatus = 0x12, + ClrIrqStatus = 0x02, + Calibrate = 0x89, + CalibrateImage = 0x98, + SetRegulatorMode = 0x96, + GetErrors = 0x17, + ClrErrors = 0x07, + SetTCXOMode = 0x97, + SetTxFallbackMode = 0x93, + SetRFSwitchMode = 0x9D, + SetStopRxTimerOnPreamble = 0x9F, + SetLoRaSymbTimeout = 0xA0, +} + +impl OpCode { + pub fn value(self) -> u8 { + self as u8 + } +} + +pub struct SleepParams { + pub wakeup_rtc: bool, // get out of sleep mode if wakeup signal received from RTC + pub reset: bool, + pub warm_start: bool, +} + +impl SleepParams { + pub fn value(self) -> u8 { + ((self.warm_start as u8) << 2) | ((self.reset as u8) << 1) | (self.wakeup_rtc as u8) + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum StandbyMode { + RC = 0x00, + XOSC = 0x01, +} + +impl StandbyMode { + pub fn value(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy)] +pub enum RegulatorMode { + UseLDO = 0x00, + UseDCDC = 0x01, +} + +impl RegulatorMode { + pub fn value(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy)] +pub struct CalibrationParams { + pub rc64k_enable: bool, // calibrate RC64K clock + pub rc13m_enable: bool, // calibrate RC13M clock + pub pll_enable: bool, // calibrate PLL + pub adc_pulse_enable: bool, // calibrate ADC Pulse + pub adc_bulkn_enable: bool, // calibrate ADC bulkN + pub adc_bulkp_enable: bool, // calibrate ADC bulkP + pub img_enable: bool, +} + +impl CalibrationParams { + pub fn value(self) -> u8 { + ((self.img_enable as u8) << 6) + | ((self.adc_bulkp_enable as u8) << 5) + | ((self.adc_bulkn_enable as u8) << 4) + | ((self.adc_pulse_enable as u8) << 3) + | ((self.pll_enable as u8) << 2) + | ((self.rc13m_enable as u8) << 1) + | ((self.rc64k_enable as u8) << 0) + } +} + +#[derive(Clone, Copy)] +pub enum TcxoCtrlVoltage { + Ctrl1V6 = 0x00, + Ctrl1V7 = 0x01, + Ctrl1V8 = 0x02, + Ctrl2V2 = 0x03, + Ctrl2V4 = 0x04, + Ctrl2V7 = 0x05, + Ctrl3V0 = 0x06, + Ctrl3V3 = 0x07, +} + +impl TcxoCtrlVoltage { + pub fn value(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy)] +pub enum RampTime { + Ramp10Us = 0x00, + Ramp20Us = 0x01, + Ramp40Us = 0x02, + Ramp80Us = 0x03, + Ramp200Us = 0x04, + Ramp800Us = 0x05, + Ramp1700Us = 0x06, + Ramp3400Us = 0x07, +} + +impl RampTime { + pub fn value(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum SpreadingFactor { + _5 = 0x05, + _6 = 0x06, + _7 = 0x07, + _8 = 0x08, + _9 = 0x09, + _10 = 0x0A, + _11 = 0x0B, + _12 = 0x0C, +} + +impl SpreadingFactor { + pub fn value(self) -> u8 { + self as u8 + } +} + +impl From for SpreadingFactor { + fn from(sf: device::SpreadingFactor) -> Self { + match sf { + device::SpreadingFactor::_7 => SpreadingFactor::_7, + device::SpreadingFactor::_8 => SpreadingFactor::_8, + device::SpreadingFactor::_9 => SpreadingFactor::_9, + device::SpreadingFactor::_10 => SpreadingFactor::_10, + device::SpreadingFactor::_11 => SpreadingFactor::_11, + device::SpreadingFactor::_12 => SpreadingFactor::_12, + } + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Bandwidth { + _500KHz = 0x06, + _250KHz = 0x05, + _125KHz = 0x04, +} + +impl Bandwidth { + pub fn value(self) -> u8 { + self as u8 + } + + pub fn value_in_hz(self) -> u32 { + match self { + Bandwidth::_125KHz => 125000u32, + Bandwidth::_250KHz => 250000u32, + Bandwidth::_500KHz => 500000u32, + } + } +} + +impl From for Bandwidth { + fn from(bw: device::Bandwidth) -> Self { + match bw { + device::Bandwidth::_500KHz => Bandwidth::_500KHz, + device::Bandwidth::_250KHz => Bandwidth::_250KHz, + device::Bandwidth::_125KHz => Bandwidth::_125KHz, + } + } +} + +#[derive(Clone, Copy)] +pub enum CodingRate { + _4_5 = 0x01, + _4_6 = 0x02, + _4_7 = 0x03, + _4_8 = 0x04, +} + +impl CodingRate { + pub fn value(self) -> u8 { + self as u8 + } +} + +impl From for CodingRate { + fn from(cr: device::CodingRate) -> Self { + match cr { + device::CodingRate::_4_5 => CodingRate::_4_5, + device::CodingRate::_4_6 => CodingRate::_4_6, + device::CodingRate::_4_7 => CodingRate::_4_7, + device::CodingRate::_4_8 => CodingRate::_4_8, + } + } +} + +#[derive(Clone, Copy)] +pub struct ModulationParams { + pub spreading_factor: SpreadingFactor, + pub bandwidth: Bandwidth, + pub coding_rate: CodingRate, + pub low_data_rate_optimize: u8, +} + +#[derive(Clone, Copy)] +pub struct PacketParams { + pub preamble_length: u16, // number of LoRa symbols in the preamble + pub implicit_header: bool, // if the header is explicit, it will be transmitted in the LoRa packet, but is not transmitted if the header is implicit (known fixed length) + pub payload_length: u8, + pub crc_on: bool, + pub iq_inverted: bool, +} + +#[derive(Clone, Copy)] +pub enum CADSymbols { + _1 = 0x00, + _2 = 0x01, + _4 = 0x02, + _8 = 0x03, + _16 = 0x04, +} + +impl CADSymbols { + pub fn value(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy)] +pub enum CADExitMode { + CADOnly = 0x00, + CADRx = 0x01, + CADLBT = 0x10, +} + +impl CADExitMode { + pub fn value(self) -> u8 { + self as u8 + } +} diff --git a/embassy-lora/src/sx126x/sx126x_lora/subroutine.rs b/embassy-lora/src/sx126x/sx126x_lora/subroutine.rs new file mode 100644 index 000000000..2e78b919b --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/subroutine.rs @@ -0,0 +1,674 @@ +use embedded_hal::digital::v2::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiBus; + +use super::mod_params::*; +use super::LoRa; + +// Internal frequency of the radio +const SX126X_XTAL_FREQ: u32 = 32000000; + +// Scaling factor used to perform fixed-point operations +const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14; + +// PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT +const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT); + +// Maximum value for parameter symbNum +const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248; + +// Provides board-specific functionality for Semtech SX126x-based boards + +impl LoRa +where + SPI: SpiBus, + CTRL: OutputPin, + WAIT: Wait, +{ + // Initialize the radio driver + pub(super) async fn sub_init(&mut self) -> Result<(), RadioError> { + self.brd_reset().await?; + self.brd_wakeup().await?; + self.sub_set_standby(StandbyMode::RC).await?; + self.brd_io_tcxo_init().await?; + self.brd_io_rf_switch_init().await?; + self.image_calibrated = false; + Ok(()) + } + + // Wakeup the radio if it is in Sleep mode and check that Busy is low + pub(super) async fn sub_check_device_ready(&mut self) -> Result<(), RadioError> { + let operating_mode = self.brd_get_operating_mode(); + if operating_mode == RadioMode::Sleep || operating_mode == RadioMode::ReceiveDutyCycle { + self.brd_wakeup().await?; + } + self.brd_wait_on_busy().await?; + Ok(()) + } + + // Save the payload to be sent in the radio buffer + pub(super) async fn sub_set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError> { + self.brd_write_buffer(0x00, payload).await?; + Ok(()) + } + + // Read the payload received. + pub(super) async fn sub_get_payload(&mut self, buffer: &mut [u8]) -> Result> { + let (size, offset) = self.sub_get_rx_buffer_status().await?; + if (size as usize) > buffer.len() { + Err(RadioError::PayloadSizeMismatch(size as usize, buffer.len())) + } else { + self.brd_read_buffer(offset, buffer).await?; + Ok(size) + } + } + + // Send a payload + pub(super) async fn sub_send_payload(&mut self, payload: &[u8], timeout: u32) -> Result<(), RadioError> { + self.sub_set_payload(payload).await?; + self.sub_set_tx(timeout).await?; + Ok(()) + } + + // Get a 32-bit random value generated by the radio. A valid packet type must have been configured before using this command. + // + // The radio must be in reception mode before executing this function. This code can potentially result in interrupt generation. It is the responsibility of + // the calling code to disable radio interrupts before calling this function, and re-enable them afterwards if necessary, or be certain that any interrupts + // generated during this process will not cause undesired side-effects in the software. + // + // The random numbers produced by the generator do not have a uniform or Gaussian distribution. If uniformity is needed, perform appropriate software post-processing. + pub(super) async fn sub_get_random(&mut self) -> Result> { + let mut reg_ana_lna_buffer_original = [0x00u8]; + let mut reg_ana_mixer_buffer_original = [0x00u8]; + let mut reg_ana_lna_buffer = [0x00u8]; + let mut reg_ana_mixer_buffer = [0x00u8]; + let mut number_buffer = [0x00u8, 0x00u8, 0x00u8, 0x00u8]; + + self.brd_read_registers(Register::AnaLNA, &mut reg_ana_lna_buffer_original) + .await?; + reg_ana_lna_buffer[0] = reg_ana_lna_buffer_original[0] & (!(1 << 0)); + self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer).await?; + + self.brd_read_registers(Register::AnaMixer, &mut reg_ana_mixer_buffer_original) + .await?; + reg_ana_mixer_buffer[0] = reg_ana_mixer_buffer_original[0] & (!(1 << 7)); + self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer) + .await?; + + // Set radio in continuous reception + self.sub_set_rx(0xFFFFFFu32).await?; + + self.brd_read_registers(Register::GeneratedRandomNumber, &mut number_buffer) + .await?; + + self.sub_set_standby(StandbyMode::RC).await?; + + self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer_original) + .await?; + self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer_original) + .await?; + + Ok(Self::convert_u8_buffer_to_u32(&number_buffer)) + } + + // Set the radio in sleep mode + pub(super) async fn sub_set_sleep(&mut self, sleep_config: SleepParams) -> Result<(), RadioError> { + self.brd_ant_sleep()?; + + if !sleep_config.warm_start { + self.image_calibrated = false; + } + + self.brd_write_command(OpCode::SetSleep, &[sleep_config.value()]) + .await?; + self.brd_set_operating_mode(RadioMode::Sleep); + Ok(()) + } + + // Set the radio in configuration mode + pub(super) async fn sub_set_standby(&mut self, mode: StandbyMode) -> Result<(), RadioError> { + self.brd_write_command(OpCode::SetStandby, &[mode.value()]).await?; + if mode == StandbyMode::RC { + self.brd_set_operating_mode(RadioMode::StandbyRC); + } else { + self.brd_set_operating_mode(RadioMode::StandbyXOSC); + } + + self.brd_ant_sleep()?; + Ok(()) + } + + // Set the radio in FS mode + pub(super) async fn sub_set_fs(&mut self) -> Result<(), RadioError> { + // antenna settings ??? + self.brd_write_command(OpCode::SetFS, &[]).await?; + self.brd_set_operating_mode(RadioMode::FrequencySynthesis); + Ok(()) + } + + // Set the radio in transmission mode with timeout specified + pub(super) async fn sub_set_tx(&mut self, timeout: u32) -> Result<(), RadioError> { + let buffer = [ + Self::timeout_1(timeout), + Self::timeout_2(timeout), + Self::timeout_3(timeout), + ]; + + self.brd_ant_set_tx()?; + + self.brd_set_operating_mode(RadioMode::Transmit); + self.brd_write_command(OpCode::SetTx, &buffer).await?; + Ok(()) + } + + // Set the radio in reception mode with timeout specified + pub(super) async fn sub_set_rx(&mut self, timeout: u32) -> Result<(), RadioError> { + let buffer = [ + Self::timeout_1(timeout), + Self::timeout_2(timeout), + Self::timeout_3(timeout), + ]; + + self.brd_ant_set_rx()?; + + self.brd_set_operating_mode(RadioMode::Receive); + self.brd_write_registers(Register::RxGain, &[0x94u8]).await?; + self.brd_write_command(OpCode::SetRx, &buffer).await?; + Ok(()) + } + + // Set the radio in reception mode with Boosted LNA gain and timeout specified + pub(super) async fn sub_set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError> { + let buffer = [ + Self::timeout_1(timeout), + Self::timeout_2(timeout), + Self::timeout_3(timeout), + ]; + + self.brd_ant_set_rx()?; + + self.brd_set_operating_mode(RadioMode::Receive); + // set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity + self.brd_write_registers(Register::RxGain, &[0x96u8]).await?; + self.brd_write_command(OpCode::SetRx, &buffer).await?; + Ok(()) + } + + // Set the Rx duty cycle management parameters + pub(super) async fn sub_set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError> { + let buffer = [ + ((rx_time >> 16) & 0xFF) as u8, + ((rx_time >> 8) & 0xFF) as u8, + (rx_time & 0xFF) as u8, + ((sleep_time >> 16) & 0xFF) as u8, + ((sleep_time >> 8) & 0xFF) as u8, + (sleep_time & 0xFF) as u8, + ]; + + // antenna settings ??? + + self.brd_write_command(OpCode::SetRxDutyCycle, &buffer).await?; + self.brd_set_operating_mode(RadioMode::ReceiveDutyCycle); + Ok(()) + } + + // Set the radio in CAD mode + pub(super) async fn sub_set_cad(&mut self) -> Result<(), RadioError> { + self.brd_ant_set_rx()?; + + self.brd_write_command(OpCode::SetCAD, &[]).await?; + self.brd_set_operating_mode(RadioMode::ChannelActivityDetection); + Ok(()) + } + + // Set the radio in continuous wave transmission mode + pub(super) async fn sub_set_tx_continuous_wave(&mut self) -> Result<(), RadioError> { + self.brd_ant_set_tx()?; + + self.brd_write_command(OpCode::SetTxContinuousWave, &[]).await?; + self.brd_set_operating_mode(RadioMode::Transmit); + Ok(()) + } + + // Set the radio in continuous preamble transmission mode + pub(super) async fn sub_set_tx_infinite_preamble(&mut self) -> Result<(), RadioError> { + self.brd_ant_set_tx()?; + + self.brd_write_command(OpCode::SetTxContinuousPremable, &[]).await?; + self.brd_set_operating_mode(RadioMode::Transmit); + Ok(()) + } + + // Decide which interrupt will stop the internal radio rx timer. + // false timer stop after header/syncword detection + // true timer stop after preamble detection + pub(super) async fn sub_set_stop_rx_timer_on_preamble_detect( + &mut self, + enable: bool, + ) -> Result<(), RadioError> { + self.brd_write_command(OpCode::SetStopRxTimerOnPreamble, &[enable as u8]) + .await?; + Ok(()) + } + + // Set the number of symbols the radio will wait to validate a reception + pub(super) async fn sub_set_lora_symb_num_timeout(&mut self, symb_num: u16) -> Result<(), RadioError> { + let mut exp = 0u8; + let mut reg; + let mut mant = ((core::cmp::min(symb_num, SX126X_MAX_LORA_SYMB_NUM_TIMEOUT as u16) as u8) + 1) >> 1; + while mant > 31 { + mant = (mant + 3) >> 2; + exp += 1; + } + reg = mant << ((2 * exp) + 1); + + self.brd_write_command(OpCode::SetLoRaSymbTimeout, &[reg]).await?; + + if symb_num != 0 { + reg = exp + (mant << 3); + self.brd_write_registers(Register::SynchTimeout, &[reg]).await?; + } + + Ok(()) + } + + // Set the power regulators operating mode (LDO or DC_DC). Using only LDO implies that the Rx or Tx current is doubled + pub(super) async fn sub_set_regulator_mode(&mut self, mode: RegulatorMode) -> Result<(), RadioError> { + self.brd_write_command(OpCode::SetRegulatorMode, &[mode.value()]) + .await?; + Ok(()) + } + + // Calibrate the given radio block + pub(super) async fn sub_calibrate(&mut self, calibrate_params: CalibrationParams) -> Result<(), RadioError> { + self.brd_write_command(OpCode::Calibrate, &[calibrate_params.value()]) + .await?; + Ok(()) + } + + // Calibrate the image rejection based on the given frequency + pub(super) async fn sub_calibrate_image(&mut self, freq: u32) -> Result<(), RadioError> { + let mut cal_freq = [0x00u8, 0x00u8]; + + if freq > 900000000 { + cal_freq[0] = 0xE1; + cal_freq[1] = 0xE9; + } else if freq > 850000000 { + cal_freq[0] = 0xD7; + cal_freq[1] = 0xDB; + } else if freq > 770000000 { + cal_freq[0] = 0xC1; + cal_freq[1] = 0xC5; + } else if freq > 460000000 { + cal_freq[0] = 0x75; + cal_freq[1] = 0x81; + } else if freq > 425000000 { + cal_freq[0] = 0x6B; + cal_freq[1] = 0x6F; + } + self.brd_write_command(OpCode::CalibrateImage, &cal_freq).await?; + Ok(()) + } + + // Activate the extention of the timeout when a long preamble is used + pub(super) async fn sub_set_long_preamble(&mut self, _enable: u8) -> Result<(), RadioError> { + Ok(()) // no operation currently + } + + // Set the transmission parameters + // hp_max 0 for sx1261, 7 for sx1262 + // device_sel 1 for sx1261, 0 for sx1262 + // pa_lut 0 for 14dBm LUT, 1 for 22dBm LUT + pub(super) async fn sub_set_pa_config( + &mut self, + pa_duty_cycle: u8, + hp_max: u8, + device_sel: u8, + pa_lut: u8, + ) -> Result<(), RadioError> { + self.brd_write_command(OpCode::SetPAConfig, &[pa_duty_cycle, hp_max, device_sel, pa_lut]) + .await?; + Ok(()) + } + + // Define into which mode the chip goes after a TX / RX done + pub(super) async fn sub_set_rx_tx_fallback_mode(&mut self, fallback_mode: u8) -> Result<(), RadioError> { + self.brd_write_command(OpCode::SetTxFallbackMode, &[fallback_mode]) + .await?; + Ok(()) + } + + // Set the IRQ mask and DIO masks + pub(super) async fn sub_set_dio_irq_params( + &mut self, + irq_mask: u16, + dio1_mask: u16, + dio2_mask: u16, + dio3_mask: u16, + ) -> Result<(), RadioError> { + let mut buffer = [0x00u8; 8]; + + buffer[0] = ((irq_mask >> 8) & 0x00FF) as u8; + buffer[1] = (irq_mask & 0x00FF) as u8; + buffer[2] = ((dio1_mask >> 8) & 0x00FF) as u8; + buffer[3] = (dio1_mask & 0x00FF) as u8; + buffer[4] = ((dio2_mask >> 8) & 0x00FF) as u8; + buffer[5] = (dio2_mask & 0x00FF) as u8; + buffer[6] = ((dio3_mask >> 8) & 0x00FF) as u8; + buffer[7] = (dio3_mask & 0x00FF) as u8; + self.brd_write_command(OpCode::CfgDIOIrq, &buffer).await?; + Ok(()) + } + + // Return the current IRQ status + pub(super) async fn sub_get_irq_status(&mut self) -> Result> { + let mut irq_status = [0x00u8, 0x00u8]; + self.brd_read_command(OpCode::GetIrqStatus, &mut irq_status).await?; + Ok(((irq_status[0] as u16) << 8) | (irq_status[1] as u16)) + } + + // Indicate if DIO2 is used to control an RF Switch + pub(super) async fn sub_set_dio2_as_rf_switch_ctrl(&mut self, enable: bool) -> Result<(), RadioError> { + self.brd_write_command(OpCode::SetRFSwitchMode, &[enable as u8]).await?; + Ok(()) + } + + // Indicate if the radio main clock is supplied from a TCXO + // tcxo_voltage voltage used to control the TCXO on/off from DIO3 + // timeout duration given to the TCXO to go to 32MHz + pub(super) async fn sub_set_dio3_as_tcxo_ctrl( + &mut self, + tcxo_voltage: TcxoCtrlVoltage, + timeout: u32, + ) -> Result<(), RadioError> { + let buffer = [ + tcxo_voltage.value() & 0x07, + Self::timeout_1(timeout), + Self::timeout_2(timeout), + Self::timeout_3(timeout), + ]; + self.brd_write_command(OpCode::SetTCXOMode, &buffer).await?; + + Ok(()) + } + + // Set the RF frequency (Hz) + pub(super) async fn sub_set_rf_frequency(&mut self, frequency: u32) -> Result<(), RadioError> { + let mut buffer = [0x00u8; 4]; + + if !self.image_calibrated { + self.sub_calibrate_image(frequency).await?; + self.image_calibrated = true; + } + + let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency); + + buffer[0] = ((freq_in_pll_steps >> 24) & 0xFF) as u8; + buffer[1] = ((freq_in_pll_steps >> 16) & 0xFF) as u8; + buffer[2] = ((freq_in_pll_steps >> 8) & 0xFF) as u8; + buffer[3] = (freq_in_pll_steps & 0xFF) as u8; + self.brd_write_command(OpCode::SetRFFrequency, &buffer).await?; + Ok(()) + } + + // Set the radio for the given protocol (LoRa or GFSK). This method has to be called before setting RF frequency, modulation paramaters, and packet paramaters. + pub(super) async fn sub_set_packet_type(&mut self, packet_type: PacketType) -> Result<(), RadioError> { + self.packet_type = packet_type; + self.brd_write_command(OpCode::SetPacketType, &[packet_type.value()]) + .await?; + Ok(()) + } + + // Get the current radio protocol (LoRa or GFSK) + pub(super) fn sub_get_packet_type(&mut self) -> PacketType { + self.packet_type + } + + // Set the transmission parameters + // power RF output power [-18..13] dBm + // ramp_time transmission ramp up time + pub(super) async fn sub_set_tx_params( + &mut self, + mut power: i8, + ramp_time: RampTime, + ) -> Result<(), RadioError> { + if self.brd_get_radio_type() == RadioType::SX1261 { + if power == 15 { + self.sub_set_pa_config(0x06, 0x00, 0x01, 0x01).await?; + } else { + self.sub_set_pa_config(0x04, 0x00, 0x01, 0x01).await?; + } + + if power >= 14 { + power = 14; + } else if power < -17 { + power = -17; + } + } else { + // Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2) + let mut tx_clamp_cfg = [0x00u8]; + self.brd_read_registers(Register::TxClampCfg, &mut tx_clamp_cfg).await?; + tx_clamp_cfg[0] = tx_clamp_cfg[0] | (0x0F << 1); + self.brd_write_registers(Register::TxClampCfg, &tx_clamp_cfg).await?; + + self.sub_set_pa_config(0x04, 0x07, 0x00, 0x01).await?; + + if power > 22 { + power = 22; + } else if power < -9 { + power = -9; + } + } + + // power conversion of negative number from i8 to u8 ??? + self.brd_write_command(OpCode::SetTxParams, &[power as u8, ramp_time.value()]) + .await?; + Ok(()) + } + + // Set the modulation parameters + pub(super) async fn sub_set_modulation_params(&mut self) -> Result<(), RadioError> { + if self.modulation_params.is_some() { + let mut buffer = [0x00u8; 4]; + + // Since this driver only supports LoRa, ensure the packet type is set accordingly + self.sub_set_packet_type(PacketType::LoRa).await?; + + let modulation_params = self.modulation_params.unwrap(); + buffer[0] = modulation_params.spreading_factor.value(); + buffer[1] = modulation_params.bandwidth.value(); + buffer[2] = modulation_params.coding_rate.value(); + buffer[3] = modulation_params.low_data_rate_optimize; + + self.brd_write_command(OpCode::SetModulationParams, &buffer).await?; + Ok(()) + } else { + Err(RadioError::ModulationParamsMissing) + } + } + + // Set the packet parameters + pub(super) async fn sub_set_packet_params(&mut self) -> Result<(), RadioError> { + if self.packet_params.is_some() { + let mut buffer = [0x00u8; 6]; + + // Since this driver only supports LoRa, ensure the packet type is set accordingly + self.sub_set_packet_type(PacketType::LoRa).await?; + + let packet_params = self.packet_params.unwrap(); + buffer[0] = ((packet_params.preamble_length >> 8) & 0xFF) as u8; + buffer[1] = (packet_params.preamble_length & 0xFF) as u8; + buffer[2] = packet_params.implicit_header as u8; + buffer[3] = packet_params.payload_length; + buffer[4] = packet_params.crc_on as u8; + buffer[5] = packet_params.iq_inverted as u8; + + self.brd_write_command(OpCode::SetPacketParams, &buffer).await?; + Ok(()) + } else { + Err(RadioError::PacketParamsMissing) + } + } + + // Set the channel activity detection (CAD) parameters + // symbols number of symbols to use for CAD operations + // det_peak limit for detection of SNR peak used in the CAD + // det_min minimum symbol recognition for CAD + // exit_mode operation to be done at the end of CAD action + // timeout timeout value to abort the CAD activity + + pub(super) async fn sub_set_cad_params( + &mut self, + symbols: CADSymbols, + det_peak: u8, + det_min: u8, + exit_mode: CADExitMode, + timeout: u32, + ) -> Result<(), RadioError> { + let mut buffer = [0x00u8; 7]; + + buffer[0] = symbols.value(); + buffer[1] = det_peak; + buffer[2] = det_min; + buffer[3] = exit_mode.value(); + buffer[4] = Self::timeout_1(timeout); + buffer[5] = Self::timeout_2(timeout); + buffer[6] = Self::timeout_3(timeout); + + self.brd_write_command(OpCode::SetCADParams, &buffer).await?; + self.brd_set_operating_mode(RadioMode::ChannelActivityDetection); + Ok(()) + } + + // Set the data buffer base address for transmission and reception + pub(super) async fn sub_set_buffer_base_address( + &mut self, + tx_base_address: u8, + rx_base_address: u8, + ) -> Result<(), RadioError> { + self.brd_write_command(OpCode::SetBufferBaseAddress, &[tx_base_address, rx_base_address]) + .await?; + Ok(()) + } + + // Get the current radio status + pub(super) async fn sub_get_status(&mut self) -> Result> { + let status = self.brd_read_command(OpCode::GetStatus, &mut []).await?; + Ok(RadioStatus { + cmd_status: (status & (0x07 << 1)) >> 1, + chip_mode: (status & (0x07 << 4)) >> 4, + }) + } + + // Get the instantaneous RSSI value for the last packet received + pub(super) async fn sub_get_rssi_inst(&mut self) -> Result> { + let mut buffer = [0x00u8]; + self.brd_read_command(OpCode::GetRSSIInst, &mut buffer).await?; + let rssi: i8 = ((-(buffer[0] as i32)) >> 1) as i8; // check this ??? + Ok(rssi) + } + + // Get the last received packet buffer status + pub(super) async fn sub_get_rx_buffer_status(&mut self) -> Result<(u8, u8), RadioError> { + if self.packet_params.is_some() { + let mut status = [0x00u8; 2]; + let mut payload_length_buffer = [0x00u8]; + + self.brd_read_command(OpCode::GetRxBufferStatus, &mut status).await?; + if (self.sub_get_packet_type() == PacketType::LoRa) && self.packet_params.unwrap().implicit_header { + self.brd_read_registers(Register::PayloadLength, &mut payload_length_buffer) + .await?; + } else { + payload_length_buffer[0] = status[0]; + } + + let payload_length = payload_length_buffer[0]; + let offset = status[1]; + + Ok((payload_length, offset)) + } else { + Err(RadioError::PacketParamsMissing) + } + } + + // Get the last received packet payload status + pub(super) async fn sub_get_packet_status(&mut self) -> Result> { + let mut status = [0x00u8; 3]; + self.brd_read_command(OpCode::GetPacketStatus, &mut status).await?; + + // check this ??? + let rssi = ((-(status[0] as i32)) >> 1) as i8; + let snr = ((status[1] as i8) + 2) >> 2; + let signal_rssi = ((-(status[2] as i32)) >> 1) as i8; + let freq_error = self.frequency_error; + + Ok(PacketStatus { + rssi, + snr, + signal_rssi, + freq_error, + }) + } + + // Get the possible system errors + pub(super) async fn sub_get_device_errors(&mut self) -> Result> { + let mut errors = [0x00u8; 2]; + self.brd_read_command(OpCode::GetErrors, &mut errors).await?; + + Ok(RadioSystemError { + rc_64khz_calibration: (errors[1] & (1 << 0)) != 0, + rc_13mhz_calibration: (errors[1] & (1 << 1)) != 0, + pll_calibration: (errors[1] & (1 << 2)) != 0, + adc_calibration: (errors[1] & (1 << 3)) != 0, + image_calibration: (errors[1] & (1 << 4)) != 0, + xosc_start: (errors[1] & (1 << 5)) != 0, + pll_lock: (errors[1] & (1 << 6)) != 0, + pa_ramp: (errors[0] & (1 << 0)) != 0, + }) + } + + // Clear all the errors in the device + pub(super) async fn sub_clear_device_errors(&mut self) -> Result<(), RadioError> { + self.brd_write_command(OpCode::ClrErrors, &[0x00u8, 0x00u8]).await?; + Ok(()) + } + + // Clear the IRQs + pub(super) async fn sub_clear_irq_status(&mut self, irq: u16) -> Result<(), RadioError> { + let mut buffer = [0x00u8, 0x00u8]; + buffer[0] = ((irq >> 8) & 0xFF) as u8; + buffer[1] = (irq & 0xFF) as u8; + self.brd_write_command(OpCode::ClrIrqStatus, &buffer).await?; + Ok(()) + } + + // Utility functions + + fn timeout_1(timeout: u32) -> u8 { + ((timeout >> 16) & 0xFF) as u8 + } + fn timeout_2(timeout: u32) -> u8 { + ((timeout >> 8) & 0xFF) as u8 + } + fn timeout_3(timeout: u32) -> u8 { + (timeout & 0xFF) as u8 + } + + // check this ??? + fn convert_u8_buffer_to_u32(buffer: &[u8; 4]) -> u32 { + let b0 = buffer[0] as u32; + let b1 = buffer[1] as u32; + let b2 = buffer[2] as u32; + let b3 = buffer[3] as u32; + (b0 << 24) | (b1 << 16) | (b2 << 8) | b3 + } + + fn convert_freq_in_hz_to_pll_step(freq_in_hz: u32) -> u32 { + // Get integer and fractional parts of the frequency computed with a PLL step scaled value + let steps_int = freq_in_hz / SX126X_PLL_STEP_SCALED; + let steps_frac = freq_in_hz - (steps_int * SX126X_PLL_STEP_SCALED); + + (steps_int << SX126X_PLL_STEP_SHIFT_AMOUNT) + + (((steps_frac << SX126X_PLL_STEP_SHIFT_AMOUNT) + (SX126X_PLL_STEP_SCALED >> 1)) / SX126X_PLL_STEP_SCALED) + } +} diff --git a/embassy-lora/src/sx127x/mod.rs b/embassy-lora/src/sx127x/mod.rs index f47a9eb55..8904c9a13 100644 --- a/embassy-lora/src/sx127x/mod.rs +++ b/embassy-lora/src/sx127x/mod.rs @@ -70,10 +70,10 @@ where RFS: RadioSwitch + 'static, { fn get_rx_window_offset_ms(&self) -> i32 { - -500 + -3 } fn get_rx_window_duration_ms(&self) -> u32 { - 800 + 1003 } } diff --git a/embassy-macros/Cargo.toml b/embassy-macros/Cargo.toml index 91d5ec8a3..5c612c997 100644 --- a/embassy-macros/Cargo.toml +++ b/embassy-macros/Cargo.toml @@ -3,6 +3,13 @@ name = "embassy-macros" version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" +description = "macros for creating the entry point and tasks for embassy-executor" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] [dependencies] syn = { version = "1.0.76", features = ["full", "extra-traits"] } @@ -14,8 +21,5 @@ proc-macro2 = "1.0.29" proc-macro = true [features] -std = [] -wasm = [] - # Enabling this cause interrupt::take! to require embassy-executor rtos-trace-interrupt = [] diff --git a/embassy-macros/README.md b/embassy-macros/README.md new file mode 100644 index 000000000..d1d6f4cc4 --- /dev/null +++ b/embassy-macros/README.md @@ -0,0 +1,21 @@ +# embassy-macros + +An [Embassy](https://embassy.dev) project. + +Macros for creating the main entry point and tasks that can be spawned by `embassy-executor`. + +NOTE: The macros are re-exported by the `embassy-executor` crate which should be used instead of adding a direct dependency on the `embassy-macros` crate. + +## Minimum supported Rust version (MSRV) + +The `task` and `main` macros require the type alias impl trait (TAIT) nightly feature in order to compile. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs index ec8498f9f..d2c696c72 100644 --- a/embassy-macros/src/lib.rs +++ b/embassy-macros/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] extern crate proc_macro; use proc_macro::TokenStream; @@ -6,6 +7,36 @@ mod macros; mod util; use macros::*; +/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how +/// many concurrent tasks can be spawned (default is 1) for the function. +/// +/// +/// The following restrictions apply: +/// +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * The optional `pool_size` attribute must be 1 or greater. +/// +/// +/// ## Examples +/// +/// Declaring a task taking no arguments: +/// +/// ``` rust +/// #[embassy_executor::task] +/// async fn mytask() { +/// // Function body +/// } +/// ``` +/// +/// Declaring a task with a given pool size: +/// +/// ``` rust +/// #[embassy_executor::task(pool_size = 4)] +/// async fn mytask() { +/// // Function body +/// } +/// ``` #[proc_macro_attribute] pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { let args = syn::parse_macro_input!(args as syn::AttributeArgs); @@ -14,11 +45,104 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { task::run(args, f).unwrap_or_else(|x| x).into() } +/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` #[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { +pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { let args = syn::parse_macro_input!(args as syn::AttributeArgs); let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(args, f).unwrap_or_else(|x| x).into() + main::run(args, f, main::cortex_m()).unwrap_or_else(|x| x).into() +} + +/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(args, f, main::riscv()).unwrap_or_else(|x| x).into() +} + +/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(args, f, main::std()).unwrap_or_else(|x| x).into() +} + +/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(args, f, main::wasm()).unwrap_or_else(|x| x).into() } #[proc_macro_attribute] diff --git a/embassy-macros/src/macros/cortex_m_interrupt_declare.rs b/embassy-macros/src/macros/cortex_m_interrupt_declare.rs index ab61ad5da..ebbb47cd7 100644 --- a/embassy-macros/src/macros/cortex_m_interrupt_declare.rs +++ b/embassy-macros/src/macros/cortex_m_interrupt_declare.rs @@ -6,7 +6,10 @@ pub fn run(name: syn::Ident) -> Result { let name_interrupt = format_ident!("{}", name); let name_handler = format!("__EMBASSY_{}_HANDLER", name); + let doc = format!("{} interrupt singleton.", name); + let result = quote! { + #[doc = #doc] #[allow(non_camel_case_types)] pub struct #name_interrupt(()); unsafe impl ::embassy_cortex_m::interrupt::Interrupt for #name_interrupt { diff --git a/embassy-macros/src/macros/main.rs b/embassy-macros/src/macros/main.rs index afe9bd3e2..18f7c36c4 100644 --- a/embassy-macros/src/macros/main.rs +++ b/embassy-macros/src/macros/main.rs @@ -7,7 +7,62 @@ use crate::util::ctxt::Ctxt; #[derive(Debug, FromMeta)] struct Args {} -pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result { +pub fn riscv() -> TokenStream { + quote! { + #[riscv_rt::entry] + fn main() -> ! { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + } +} + +pub fn cortex_m() -> TokenStream { + quote! { + #[cortex_m_rt::entry] + fn main() -> ! { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + } +} + +pub fn wasm() -> TokenStream { + quote! { + #[wasm_bindgen::prelude::wasm_bindgen(start)] + pub fn main() -> Result<(), wasm_bindgen::JsValue> { + static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new(); + let executor = EXECUTOR.init(::embassy_executor::Executor::new()); + + executor.start(|spawner| { + spawner.spawn(__embassy_main(spawner)).unwrap(); + }); + + Ok(()) + } + } +} + +pub fn std() -> TokenStream { + quote! { + fn main() -> ! { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + } +} + +pub fn run(args: syn::AttributeArgs, f: syn::ItemFn, main: TokenStream) -> Result { #[allow(unused_variables)] let args = Args::from_list(&args).map_err(|e| e.write_errors())?; @@ -30,46 +85,6 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result Result<(), wasm_bindgen::JsValue> { - static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new(); - let executor = EXECUTOR.init(::embassy_executor::Executor::new()); - - executor.start(|spawner| { - spawner.spawn(__embassy_main(spawner)).unwrap(); - }); - - Ok(()) - } - }; - - #[cfg(all(feature = "std", not(feature = "wasm")))] - let main = quote! { - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - }; - - #[cfg(all(not(feature = "std"), not(feature = "wasm")))] - let main = quote! { - #[cortex_m_rt::entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - }; - let result = quote! { #[::embassy_executor::task()] async fn __embassy_main(#fargs) { diff --git a/embassy-macros/src/macros/task.rs b/embassy-macros/src/macros/task.rs index 573776f8c..90d2cd893 100644 --- a/embassy-macros/src/macros/task.rs +++ b/embassy-macros/src/macros/task.rs @@ -1,6 +1,7 @@ use darling::FromMeta; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use syn::{parse_quote, ItemFn}; use crate::util::ctxt::Ctxt; @@ -57,13 +58,7 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result ::embassy_executor::SpawnToken { type Fut = impl ::core::future::Future + 'static; static POOL: ::embassy_executor::raw::TaskPool = ::embassy_executor::raw::TaskPool::new(); @@ -71,5 +66,18 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +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 ),*); + } + }; +} + +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 ),*); + } + }; +} + +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 ),*); + } + }; +} + +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 ),*); + } + }; +} + +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")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +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 + } +} diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs new file mode 100644 index 000000000..0c8dcc22b --- /dev/null +++ b/embassy-net-driver-channel/src/lib.rs @@ -0,0 +1,549 @@ +#![no_std] + +// must go first! +mod fmt; + +use core::cell::RefCell; +use core::mem::MaybeUninit; +use core::task::{Context, Poll}; + +pub use embassy_net_driver as driver; +use embassy_net_driver::{Capabilities, LinkState, Medium}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; + +pub struct State { + rx: [PacketBuf; N_RX], + tx: [PacketBuf; N_TX], + inner: MaybeUninit>, +} + +impl State { + const NEW_PACKET: PacketBuf = PacketBuf::new(); + + pub const fn new() -> Self { + Self { + rx: [Self::NEW_PACKET; N_RX], + tx: [Self::NEW_PACKET; N_TX], + inner: MaybeUninit::uninit(), + } + } +} + +struct StateInner<'d, const MTU: usize> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + shared: Mutex>, +} + +/// State of the LinkState +struct Shared { + link_state: LinkState, + waker: WakerRegistration, + ethernet_address: [u8; 6], +} + +pub struct Runner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, +} + +#[derive(Clone, Copy)] +pub struct StateRunner<'d> { + shared: &'d Mutex>, +} + +pub struct RxRunner<'d, const MTU: usize> { + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, +} + +pub struct TxRunner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, +} + +impl<'d, const MTU: usize> Runner<'d, MTU> { + pub fn split(self) -> (StateRunner<'d>, RxRunner<'d, MTU>, TxRunner<'d, MTU>) { + ( + StateRunner { shared: self.shared }, + RxRunner { rx_chan: self.rx_chan }, + TxRunner { tx_chan: self.tx_chan }, + ) + } + + pub fn state_runner(&self) -> StateRunner<'d> { + StateRunner { shared: self.shared } + } + + pub fn set_link_state(&mut self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + pub fn set_ethernet_address(&mut self, address: [u8; 6]) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.ethernet_address = address; + s.waker.wake(); + }); + } + + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } + + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.recv().await; + &mut p.buf[..p.len] + } + + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_recv()?; + Some(&mut p.buf[..p.len]) + } + + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_recv(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + pub fn tx_done(&mut self) { + self.tx_chan.recv_done(); + } +} + +impl<'d> StateRunner<'d> { + pub fn set_link_state(&self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + pub fn set_ethernet_address(&self, address: [u8; 6]) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.ethernet_address = address; + s.waker.wake(); + }); + } +} + +impl<'d, const MTU: usize> RxRunner<'d, MTU> { + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } +} + +impl<'d, const MTU: usize> TxRunner<'d, MTU> { + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.recv().await; + &mut p.buf[..p.len] + } + + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_recv()?; + Some(&mut p.buf[..p.len]) + } + + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_recv(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + pub fn tx_done(&mut self) { + self.tx_chan.recv_done(); + } +} + +pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( + state: &'d mut State, + ethernet_address: [u8; 6], +) -> (Runner<'d, MTU>, Device<'d, MTU>) { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps.medium = Medium::Ethernet; + + // 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(StateInner { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + shared: Mutex::new(RefCell::new(Shared { + link_state: LinkState::Down, + ethernet_address, + waker: WakerRegistration::new(), + })), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + Runner { + tx_chan: tx_receiver, + rx_chan: rx_sender, + shared: &state.shared, + }, + Device { + caps, + shared: &state.shared, + rx: rx_receiver, + tx: tx_sender, + }, + ) +} + +pub struct PacketBuf { + len: usize, + buf: [u8; MTU], +} + +impl PacketBuf { + pub const fn new() -> Self { + Self { len: 0, buf: [0; MTU] } + } +} + +pub struct Device<'d, const MTU: usize> { + rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, + caps: Capabilities, +} + +impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { + type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ; + type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() { + Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() })) + } else { + None + } + } + + /// Construct a transmit token. + fn transmit(&mut self, cx: &mut Context) -> Option> { + if self.tx.poll_send(cx).is_ready() { + Some(TxToken { tx: self.tx.borrow() }) + } else { + None + } + } + + /// Get a description of device capabilities. + fn capabilities(&self) -> Capabilities { + self.caps.clone() + } + + fn ethernet_address(&self) -> [u8; 6] { + self.shared.lock(|s| s.borrow().ethernet_address) + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.waker.register(cx.waker()); + s.link_state + }) + } +} + +pub struct RxToken<'a, const MTU: usize> { + rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.try_recv()); + let r = f(&mut pkt.buf[..pkt.len]); + self.rx.recv_done(); + r + } +} + +pub struct TxToken<'a, const MTU: usize> { + tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> { + fn consume(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.try_send()); + let r = f(&mut pkt.buf[..len]); + pkt.len = len; + self.tx.send_done(); + r + } +} + +mod zerocopy_channel { + use core::cell::RefCell; + use core::future::poll_fn; + use core::marker::PhantomData; + use core::task::{Context, Poll}; + + use embassy_sync::blocking_mutex::raw::RawMutex; + use embassy_sync::blocking_mutex::Mutex; + use embassy_sync::waitqueue::WakerRegistration; + + pub struct Channel<'a, M: RawMutex, T> { + buf: *mut T, + phantom: PhantomData<&'a mut T>, + state: Mutex>, + } + + impl<'a, M: RawMutex, T> Channel<'a, M, T> { + pub fn new(buf: &'a mut [T]) -> Self { + let len = buf.len(); + assert!(len != 0); + + Self { + buf: buf.as_mut_ptr(), + phantom: PhantomData, + state: Mutex::new(RefCell::new(State { + len, + front: 0, + back: 0, + full: false, + send_waker: WakerRegistration::new(), + recv_waker: WakerRegistration::new(), + })), + } + } + + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { channel: self }, Receiver { channel: self }) + } + } + + pub struct Sender<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Sender<'a, M, T> { + pub fn borrow(&mut self) -> Sender<'_, M, T> { + Sender { channel: self.channel } + } + + pub fn try_send(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn send(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(i), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn send_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().push_done()) + } + } + pub struct Receiver<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + pub fn borrow(&mut self) -> Receiver<'_, M, T> { + Receiver { channel: self.channel } + } + + pub fn try_recv(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn recv(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(i), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn recv_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().pop_done()) + } + } + + struct State { + len: usize, + + /// Front index. Always 0..=(N-1) + front: usize, + /// Back index. Always 0..=(N-1). + back: usize, + + /// Used to distinguish "empty" and "full" cases when `front == back`. + /// May only be `true` if `front == back`, always `false` otherwise. + full: bool, + + send_waker: WakerRegistration, + recv_waker: WakerRegistration, + } + + impl State { + fn increment(&self, i: usize) -> usize { + if i + 1 == self.len { + 0 + } else { + i + 1 + } + } + + fn is_full(&self) -> bool { + self.full + } + + fn is_empty(&self) -> bool { + self.front == self.back && !self.full + } + + fn push_index(&mut self) -> Option { + match self.is_full() { + true => None, + false => Some(self.back), + } + } + + fn push_done(&mut self) { + assert!(!self.is_full()); + self.back = self.increment(self.back); + if self.back == self.front { + self.full = true; + } + self.send_waker.wake(); + } + + fn pop_index(&mut self) -> Option { + match self.is_empty() { + true => None, + false => Some(self.front), + } + } + + fn pop_done(&mut self) { + assert!(!self.is_empty()); + self.front = self.increment(self.front); + self.full = false; + self.recv_waker.wake(); + } + } +} diff --git a/embassy-net-driver/Cargo.toml b/embassy-net-driver/Cargo.toml new file mode 100644 index 000000000..ff6f29355 --- /dev/null +++ b/embassy-net-driver/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "embassy-net-driver" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[dependencies] +defmt = { version = "0.3", optional = true } \ No newline at end of file diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs new file mode 100644 index 000000000..a39cfecc1 --- /dev/null +++ b/embassy-net-driver/src/lib.rs @@ -0,0 +1,175 @@ +#![no_std] + +use core::task::Context; + +pub trait Driver { + type RxToken<'a>: RxToken + where + Self: 'a; + type TxToken<'a>: TxToken + where + Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; + fn transmit(&mut self, cx: &mut Context) -> Option>; + fn link_state(&mut self, cx: &mut Context) -> LinkState; + + fn capabilities(&self) -> Capabilities; + fn ethernet_address(&self) -> [u8; 6]; +} + +impl Driver for &mut T { + type RxToken<'a> = T::RxToken<'a> + where + Self: 'a; + type TxToken<'a> = T::TxToken<'a> + where + Self: 'a; + + fn transmit(&mut self, cx: &mut Context) -> Option> { + T::transmit(self, cx) + } + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + T::receive(self, cx) + } + fn capabilities(&self) -> Capabilities { + T::capabilities(self) + } + fn link_state(&mut self, cx: &mut Context) -> LinkState { + T::link_state(self, cx) + } + fn ethernet_address(&self) -> [u8; 6] { + T::ethernet_address(self) + } +} + +/// A token to receive a single network packet. +pub trait RxToken { + /// Consumes the token to receive a single network packet. + /// + /// This method receives a packet and then calls the given closure `f` with the raw + /// packet bytes as argument. + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; +} + +/// A token to transmit a single network packet. +pub trait TxToken { + /// Consumes the token to send a single network packet. + /// + /// This method constructs a transmit buffer of size `len` and calls the passed + /// closure `f` with a mutable reference to that buffer. The closure should construct + /// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure + /// returns, the transmit buffer is sent out. + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; +} + +/// A description of device capabilities. +/// +/// Higher-level protocols may achieve higher throughput or lower latency if they consider +/// the bandwidth or packet size limitations. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Capabilities { + /// Medium of the device. + /// + /// This indicates what kind of packet the sent/received bytes are, and determines + /// some behaviors of Interface. For example, ARP/NDISC address resolution is only done + /// for Ethernet mediums. + pub medium: Medium, + + /// Maximum transmission unit. + /// + /// The network device is unable to send or receive frames larger than the value returned + /// by this function. + /// + /// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but + /// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14. + /// + /// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet + /// devices. This is a common source of confusion. + /// + /// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets. + pub max_transmission_unit: usize, + + /// Maximum burst size, in terms of MTU. + /// + /// The network device is unable to send or receive bursts large than the value returned + /// by this function. + /// + /// If `None`, there is no fixed limit on burst size, e.g. if network buffers are + /// dynamically allocated. + pub max_burst_size: Option, + + /// Checksum behavior. + /// + /// If the network device is capable of verifying or computing checksums for some protocols, + /// it can request that the stack not do so in software to improve performance. + pub checksum: ChecksumCapabilities, +} + +/// Type of medium of a device. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Medium { + /// Ethernet medium. Devices of this type send and receive Ethernet frames, + /// and interfaces using it must do neighbor discovery via ARP or NDISC. + /// + /// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode. + Ethernet, + + /// IP medium. Devices of this type send and receive IP frames, without an + /// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done. + /// + /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode. + Ip, +} + +impl Default for Medium { + fn default() -> Medium { + Medium::Ethernet + } +} + +/// A description of checksum behavior for every supported protocol. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ChecksumCapabilities { + pub ipv4: Checksum, + pub udp: Checksum, + pub tcp: Checksum, + pub icmpv4: Checksum, + pub icmpv6: Checksum, +} + +/// A description of checksum behavior for a particular protocol. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Checksum { + /// Verify checksum when receiving and compute checksum when sending. + Both, + /// Verify checksum when receiving. + Rx, + /// Compute checksum before sending. + Tx, + /// Ignore checksum completely. + None, +} + +impl Default for Checksum { + fn default() -> Checksum { + Checksum::Both + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LinkState { + Down, + Up, +} diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 967ef26a7..ca34262df 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -8,41 +8,42 @@ license = "MIT OR Apache-2.0" [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 = ["pool-4", "defmt", "tcp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"] +features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"] target = "thumbv7em-none-eabi" [features] default = [] std = [] -defmt = ["dep:defmt", "smoltcp/defmt"] +defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"] nightly = ["dep:embedded-io", "embedded-io?/async", "dep:embedded-nal-async"] unstable-traits = [] udp = ["smoltcp/socket-udp"] tcp = ["smoltcp/socket-tcp"] -dns = ["smoltcp/socket-dns"] +dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"] dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"] proto-ipv6 = ["smoltcp/proto-ipv6"] medium-ethernet = ["smoltcp/medium-ethernet"] medium-ip = ["smoltcp/medium-ip"] -pool-4 = [] -pool-8 = [] -pool-16 = [] -pool-32 = [] -pool-64 = [] -pool-128 = [] - [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } +smoltcp = { version = "0.9.0", default-features = false, features = [ + "proto-ipv4", + "socket", + "async", +]} + +embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } +embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common" } embassy-time = { version = "0.1.0", path = "../embassy-time" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embedded-io = { version = "0.3.0", optional = true } +embedded-io = { version = "0.4.0", optional = true } managed = { version = "0.8.0", default-features = false, features = [ "map" ] } heapless = { version = "0.7.5", default-features = false } @@ -51,16 +52,5 @@ generic-array = { version = "0.14.4", default-features = false } stable_deref_trait = { version = "1.2.0", default-features = false } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } atomic-pool = "1.0" -atomic-polyfill = "1.0.1" -embedded-nal-async = { version = "0.2.0", optional = true } - -[dependencies.smoltcp] -version = "0.8.0" -git = "https://github.com/smoltcp-rs/smoltcp" -rev = "ed0cf16750a42f30e31fcaf5347915592924b1e3" -default-features = false -features = [ - "proto-ipv4", - "socket", - "async", -] +embedded-nal-async = { version = "0.4.0", optional = true } +atomic-polyfill = { version = "1.0" } diff --git a/embassy-net/README.md b/embassy-net/README.md index 9657e3589..470926c58 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -17,11 +17,13 @@ sudo ip -6 route add fe80::/64 dev tap0 sudo ip -6 route add fdaa::/64 dev tap0 ``` +Second, have something listening there. For example `nc -l 8000` + Then run the example located in the `examples` folder: ```sh cd $EMBASSY_ROOT/examples/std/ -cargo run --bin net +cargo run --bin net -- --static-ip ``` ## License diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs index c183bd58a..5daa00544 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/device.rs @@ -1,129 +1,102 @@ -use core::task::Waker; +use core::task::Context; -use smoltcp::phy::{Device as SmolDevice, DeviceCapabilities}; -use smoltcp::time::Instant as SmolInstant; +use embassy_net_driver::{Capabilities, Checksum, Driver, Medium, RxToken, TxToken}; +use smoltcp::phy; +use smoltcp::time::Instant; -use crate::packet_pool::PacketBoxExt; -use crate::{Packet, PacketBox, PacketBuf}; - -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum LinkState { - Down, - Up, +pub(crate) struct DriverAdapter<'d, 'c, T> +where + T: Driver, +{ + // must be Some when actually using this to rx/tx + pub cx: Option<&'d mut Context<'c>>, + pub inner: &'d mut T, } -// 'static required due to the "fake GAT" in smoltcp::phy::Device. -// https://github.com/smoltcp-rs/smoltcp/pull/572 -pub trait Device { - fn is_transmit_ready(&mut self) -> bool; - fn transmit(&mut self, pkt: PacketBuf); - fn receive(&mut self) -> Option; +impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T> +where + T: Driver, +{ + type RxToken<'a> = RxTokenAdapter> where Self: 'a; + type TxToken<'a> = TxTokenAdapter> where Self: 'a; - fn register_waker(&mut self, waker: &Waker); - fn capabilities(&self) -> DeviceCapabilities; - fn link_state(&mut self) -> LinkState; - fn ethernet_address(&self) -> [u8; 6]; -} - -impl Device for &'static mut T { - fn is_transmit_ready(&mut self) -> bool { - T::is_transmit_ready(self) - } - fn transmit(&mut self, pkt: PacketBuf) { - T::transmit(self, pkt) - } - fn receive(&mut self) -> Option { - T::receive(self) - } - fn register_waker(&mut self, waker: &Waker) { - T::register_waker(self, waker) - } - fn capabilities(&self) -> DeviceCapabilities { - T::capabilities(self) - } - fn link_state(&mut self) -> LinkState { - T::link_state(self) - } - fn ethernet_address(&self) -> [u8; 6] { - T::ethernet_address(self) - } -} - -pub struct DeviceAdapter { - pub device: D, - caps: DeviceCapabilities, -} - -impl DeviceAdapter { - pub(crate) fn new(device: D) -> Self { - Self { - caps: device.capabilities(), - device, - } - } -} - -impl<'a, D: Device + 'static> SmolDevice<'a> for DeviceAdapter { - type RxToken = RxToken; - type TxToken = TxToken<'a, D>; - - fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { - let tx_pkt = PacketBox::new(Packet::new())?; - let rx_pkt = self.device.receive()?; - let rx_token = RxToken { pkt: rx_pkt }; - let tx_token = TxToken { - device: &mut self.device, - pkt: tx_pkt, - }; - - Some((rx_token, tx_token)) + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.inner + .receive(self.cx.as_deref_mut().unwrap()) + .map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx))) } /// Construct a transmit token. - fn transmit(&'a mut self) -> Option { - if !self.device.is_transmit_ready() { - return None; - } - - let tx_pkt = PacketBox::new(Packet::new())?; - Some(TxToken { - device: &mut self.device, - pkt: tx_pkt, - }) + fn transmit(&mut self, _timestamp: Instant) -> Option> { + self.inner.transmit(self.cx.as_deref_mut().unwrap()).map(TxTokenAdapter) } /// Get a description of device capabilities. - fn capabilities(&self) -> DeviceCapabilities { - self.caps.clone() + fn capabilities(&self) -> phy::DeviceCapabilities { + fn convert(c: Checksum) -> phy::Checksum { + match c { + Checksum::Both => phy::Checksum::Both, + Checksum::Tx => phy::Checksum::Tx, + Checksum::Rx => phy::Checksum::Rx, + Checksum::None => phy::Checksum::None, + } + } + let caps: Capabilities = self.inner.capabilities(); + let mut smolcaps = phy::DeviceCapabilities::default(); + + smolcaps.max_transmission_unit = caps.max_transmission_unit; + smolcaps.max_burst_size = caps.max_burst_size; + smolcaps.medium = match caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => phy::Medium::Ethernet, + #[cfg(feature = "medium-ip")] + Medium::Ip => phy::Medium::Ip, + _ => panic!( + "Unsupported medium {:?}. MAke sure to enable it in embassy-net's Cargo features.", + caps.medium + ), + }; + smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4); + smolcaps.checksum.tcp = convert(caps.checksum.tcp); + smolcaps.checksum.udp = convert(caps.checksum.udp); + smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4); + #[cfg(feature = "proto-ipv6")] + { + smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6); + } + + smolcaps } } -pub struct RxToken { - pkt: PacketBuf, -} +pub(crate) struct RxTokenAdapter(T) +where + T: RxToken; -impl smoltcp::phy::RxToken for RxToken { - fn consume(mut self, _timestamp: SmolInstant, f: F) -> smoltcp::Result +impl phy::RxToken for RxTokenAdapter +where + T: RxToken, +{ + fn consume(self, f: F) -> R where - F: FnOnce(&mut [u8]) -> smoltcp::Result, + F: FnOnce(&mut [u8]) -> R, { - f(&mut self.pkt) + self.0.consume(|buf| f(buf)) } } -pub struct TxToken<'a, D: Device> { - device: &'a mut D, - pkt: PacketBox, -} +pub(crate) struct TxTokenAdapter(T) +where + T: TxToken; -impl<'a, D: Device> smoltcp::phy::TxToken for TxToken<'a, D> { - fn consume(self, _timestamp: SmolInstant, len: usize, f: F) -> smoltcp::Result +impl phy::TxToken for TxTokenAdapter +where + T: TxToken, +{ + fn consume(self, len: usize, f: F) -> R where - F: FnOnce(&mut [u8]) -> smoltcp::Result, + F: FnOnce(&mut [u8]) -> R, { - let mut buf = self.pkt.slice(0..len); - let r = f(&mut buf)?; - self.device.transmit(buf); - Ok(r) + self.0.consume(len, |buf| f(buf)) } } diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs new file mode 100644 index 000000000..97320e44b --- /dev/null +++ b/embassy-net/src/dns.rs @@ -0,0 +1,97 @@ +//! DNS socket with async support. +use heapless::Vec; +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}; + +/// Errors returned by DnsSocket. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Invalid name + InvalidName, + /// Name too long + NameTooLong, + /// Name lookup failed + Failed, +} + +impl From for Error { + fn from(_: GetQueryResultError) -> Self { + Self::Failed + } +} + +impl From for Error { + fn from(e: StartQueryError) -> Self { + match e { + StartQueryError::NoFreeSlot => Self::Failed, + StartQueryError::InvalidName => Self::InvalidName, + StartQueryError::NameTooLong => Self::NameTooLong, + } + } +} + +/// Async socket for making DNS queries. +pub struct DnsSocket<'a, D> +where + D: Driver + 'static, +{ + stack: &'a Stack, +} + +impl<'a, D> DnsSocket<'a, D> +where + D: Driver + 'static, +{ + /// 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 { + Self { stack } + } + + /// Make a query for a given name and return the corresponding IP addresses. + pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result, Error> { + self.stack.dns_query(name, qtype).await + } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D> +where + D: Driver + 'static, +{ + type Error = Error; + + async fn get_host_by_name( + &self, + host: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result { + use embedded_nal_async::{AddrType, IpAddr}; + let qtype = match addr_type { + AddrType::IPv6 => DnsQueryType::Aaaa, + _ => DnsQueryType::A, + }; + let addrs = self.query(host, qtype).await?; + if let Some(first) = addrs.get(0) { + Ok(match first { + IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()), + }) + } else { + Err(Error::Failed) + } + } + + async fn get_host_by_address( + &self, + _addr: embedded_nal_async::IpAddr, + ) -> Result, Self::Error> { + todo!() + } +} diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 4d30550d3..bda5f9e14 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -1,25 +1,40 @@ #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] +#![cfg_attr( + feature = "nightly", + feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections) +)] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +pub use embassy_net_driver as driver; + mod device; -mod packet_pool; -mod stack; - -pub use device::{Device, LinkState}; -pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf, MTU}; -pub use stack::{Config, ConfigStrategy, Stack, StackResources}; - +#[cfg(feature = "dns")] +pub mod dns; #[cfg(feature = "tcp")] pub mod tcp; - #[cfg(feature = "udp")] pub mod udp; +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::task::{Context, Poll}; + +use embassy_net_driver::{Driver, LinkState, Medium}; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_time::{Instant, Timer}; +use futures::pin_mut; +use heapless::Vec; +#[cfg(feature = "dhcpv4")] +use smoltcp::iface::SocketHandle; +use smoltcp::iface::{Interface, SocketSet, SocketStorage}; +#[cfg(feature = "dhcpv4")] +use smoltcp::socket::dhcpv4; +use smoltcp::socket::dhcpv4::RetryConfig; +use smoltcp::time::Duration; // smoltcp reexports -pub use smoltcp::phy::{DeviceCapabilities, Medium}; pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; #[cfg(feature = "medium-ethernet")] pub use smoltcp::wire::{EthernetAddress, HardwareAddress}; @@ -28,3 +43,388 @@ pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}; pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; #[cfg(feature = "udp")] pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint}; + +use crate::device::DriverAdapter; + +const LOCAL_PORT_MIN: u16 = 1025; +const LOCAL_PORT_MAX: u16 = 65535; +#[cfg(feature = "dns")] +const MAX_QUERIES: usize = 4; + +pub struct StackResources { + sockets: [SocketStorage<'static>; SOCK], + #[cfg(feature = "dns")] + queries: [Option; MAX_QUERIES], +} + +impl StackResources { + pub fn new() -> Self { + #[cfg(feature = "dns")] + const INIT: Option = None; + Self { + sockets: [SocketStorage::EMPTY; SOCK], + #[cfg(feature = "dns")] + queries: [INIT; MAX_QUERIES], + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfig { + pub address: Ipv4Cidr, + pub gateway: Option, + pub dns_servers: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DhcpConfig { + pub max_lease_duration: Option, + pub retry_config: RetryConfig, + /// Ignore NAKs. + pub ignore_naks: bool, + /// Server port config + pub server_port: u16, + /// Client port config + pub client_port: u16, +} + +impl Default for DhcpConfig { + fn default() -> Self { + Self { + max_lease_duration: Default::default(), + retry_config: Default::default(), + ignore_naks: Default::default(), + server_port: smoltcp::wire::DHCP_SERVER_PORT, + client_port: smoltcp::wire::DHCP_CLIENT_PORT, + } + } +} + +pub enum Config { + Static(StaticConfig), + #[cfg(feature = "dhcpv4")] + Dhcp(DhcpConfig), +} + +pub struct Stack { + pub(crate) socket: RefCell, + inner: RefCell>, +} + +struct Inner { + device: D, + link_up: bool, + config: Option, + #[cfg(feature = "dhcpv4")] + dhcp_socket: Option, + #[cfg(feature = "dns")] + dns_socket: SocketHandle, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration, +} + +pub(crate) struct SocketStack { + pub(crate) sockets: SocketSet<'static>, + pub(crate) iface: Interface, + pub(crate) waker: WakerRegistration, + next_local_port: u16, +} + +impl Stack { + pub fn new( + mut device: D, + config: Config, + resources: &'static mut StackResources, + random_seed: u64, + ) -> Self { + #[cfg(feature = "medium-ethernet")] + let medium = device.capabilities().medium; + + let mut iface_cfg = smoltcp::iface::Config::new(); + iface_cfg.random_seed = random_seed; + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + iface_cfg.hardware_addr = Some(HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address()))); + } + + let iface = Interface::new( + iface_cfg, + &mut DriverAdapter { + inner: &mut device, + cx: None, + }, + ); + + 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; + + let mut socket = SocketStack { + sockets, + iface, + waker: WakerRegistration::new(), + next_local_port, + }; + + let mut inner = Inner { + device, + link_up: false, + config: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket: socket.sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(&mut resources.queries), + )), + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), + }; + + match config { + Config::Static(config) => { + inner.apply_config(&mut socket, config); + } + #[cfg(feature = "dhcpv4")] + Config::Dhcp(config) => { + let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new(); + inner.apply_dhcp_config(&mut dhcp_socket, config); + let handle = socket.sockets.add(dhcp_socket); + inner.dhcp_socket = Some(handle); + } + } + + Self { + socket: RefCell::new(socket), + inner: RefCell::new(inner), + } + } + + 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()) + } + + pub fn ethernet_address(&self) -> [u8; 6] { + self.with(|_s, i| i.device.ethernet_address()) + } + + pub fn is_link_up(&self) -> bool { + self.with(|_s, i| i.link_up) + } + + pub fn is_config_up(&self) -> bool { + self.with(|_s, i| i.config.is_some()) + } + + pub fn config(&self) -> Option { + self.with(|_s, i| i.config.clone()) + } + + 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(&self, name: &str, qtype: dns::DnsQueryType) -> Result, dns::Error> { + 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) { + Ok(handle) => Poll::Ready(Ok(handle)), + Err(dns::StartQueryError::NoFreeSlot) => { + i.dns_waker.register(cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e)), + } + }) + }) + .await?; + + use embassy_hal_common::drop::OnDrop; + let drop = OnDrop::new(|| { + self.with_mut(|s, i| { + let socket = s.sockets.get_mut::(i.dns_socket); + socket.cancel_query(query); + s.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); + match socket.get_query_result(query) { + Ok(addrs) => { + i.dns_waker.wake(); + Poll::Ready(Ok(addrs)) + } + Err(dns::GetQueryResultError::Pending) => { + socket.register_query_waker(query, cx.waker()); + Poll::Pending + } + Err(e) => { + i.dns_waker.wake(); + Poll::Ready(Err(e.into())) + } + } + }) + }) + .await; + + drop.defuse(); + + res + } +} + +impl SocketStack { + #[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 { + fn apply_config(&mut self, s: &mut SocketStack, config: StaticConfig) { + #[cfg(feature = "medium-ethernet")] + let medium = self.device.capabilities().medium; + + debug!("Acquired IP configuration:"); + + debug!(" IP address: {}", config.address); + s.iface.update_ip_addrs(|addrs| { + if addrs.is_empty() { + addrs.push(IpCidr::Ipv4(config.address)).unwrap(); + } else { + addrs[0] = IpCidr::Ipv4(config.address); + } + }); + + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + if let Some(gateway) = config.gateway { + debug!(" Default gateway: {}", gateway); + s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap(); + } else { + debug!(" Default gateway: None"); + s.iface.routes_mut().remove_default_ipv4_route(); + } + } + for (i, s) in config.dns_servers.iter().enumerate() { + debug!(" DNS server {}: {}", i, s); + } + + #[cfg(feature = "dns")] + { + let socket = s.sockets.get_mut::(self.dns_socket); + let servers: Vec = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); + socket.update_servers(&servers[..]); + } + + self.config = Some(config) + } + + fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) { + socket.set_ignore_naks(config.ignore_naks); + socket.set_max_lease_duration(config.max_lease_duration); + socket.set_ports(config.server_port, config.client_port); + socket.set_retry_config(config.retry_config); + } + + #[allow(unused)] // used only with dhcp + fn unapply_config(&mut self, s: &mut SocketStack) { + #[cfg(feature = "medium-ethernet")] + let medium = self.device.capabilities().medium; + + debug!("Lost IP configuration"); + s.iface.update_ip_addrs(|ip_addrs| ip_addrs.clear()); + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + s.iface.routes_mut().remove_default_ipv4_route(); + } + self.config = None + } + + fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { + s.waker.register(cx.waker()); + + #[cfg(feature = "medium-ethernet")] + if self.device.capabilities().medium == Medium::Ethernet { + s.iface.set_hardware_addr(HardwareAddress::Ethernet(EthernetAddress( + self.device.ethernet_address(), + ))); + } + + let timestamp = instant_to_smoltcp(Instant::now()); + let mut smoldev = DriverAdapter { + cx: Some(cx), + inner: &mut self.device, + }; + s.iface.poll(timestamp, &mut smoldev, &mut s.sockets); + + // Update link up + let old_link_up = self.link_up; + self.link_up = self.device.link_state(cx) == LinkState::Up; + + // Print when changed + if old_link_up != self.link_up { + info!("link_up = {:?}", self.link_up); + } + + #[cfg(feature = "dhcpv4")] + if let Some(dhcp_handle) = self.dhcp_socket { + let socket = s.sockets.get_mut::(dhcp_handle); + + if self.link_up { + match socket.poll() { + None => {} + Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s), + Some(dhcpv4::Event::Configured(config)) => { + let config = StaticConfig { + address: config.address, + gateway: config.router, + dns_servers: config.dns_servers, + }; + self.apply_config(s, config) + } + } + } else if old_link_up { + socket.reset(); + self.unapply_config(s); + } + } + //if old_link_up || self.link_up { + // self.poll_configurator(timestamp) + //} + // + + if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { + let t = Timer::at(instant_from_smoltcp(poll_at)); + pin_mut!(t); + if t.poll(cx).is_ready() { + cx.waker().wake_by_ref(); + } + } + } +} + +fn instant_to_smoltcp(instant: Instant) -> SmolInstant { + SmolInstant::from_millis(instant.as_millis() as i64) +} + +fn instant_from_smoltcp(instant: SmolInstant) -> Instant { + Instant::from_millis(instant.total_millis() as u64) +} diff --git a/embassy-net/src/packet_pool.rs b/embassy-net/src/packet_pool.rs deleted file mode 100644 index cb8a1316c..000000000 --- a/embassy-net/src/packet_pool.rs +++ /dev/null @@ -1,107 +0,0 @@ -use core::ops::{Deref, DerefMut, Range}; - -use as_slice::{AsMutSlice, AsSlice}; -use atomic_pool::{pool, Box}; - -pub const MTU: usize = 1516; - -#[cfg(feature = "pool-4")] -pub const PACKET_POOL_SIZE: usize = 4; - -#[cfg(feature = "pool-8")] -pub const PACKET_POOL_SIZE: usize = 8; - -#[cfg(feature = "pool-16")] -pub const PACKET_POOL_SIZE: usize = 16; - -#[cfg(feature = "pool-32")] -pub const PACKET_POOL_SIZE: usize = 32; - -#[cfg(feature = "pool-64")] -pub const PACKET_POOL_SIZE: usize = 64; - -#[cfg(feature = "pool-128")] -pub const PACKET_POOL_SIZE: usize = 128; - -pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]); -pub type PacketBox = Box; - -#[repr(align(4))] -pub struct Packet(pub [u8; MTU]); - -impl Packet { - pub const fn new() -> Self { - Self([0; MTU]) - } -} - -pub trait PacketBoxExt { - fn slice(self, range: Range) -> PacketBuf; -} - -impl PacketBoxExt for PacketBox { - fn slice(self, range: Range) -> PacketBuf { - PacketBuf { packet: self, range } - } -} - -impl AsSlice for Packet { - type Element = u8; - - fn as_slice(&self) -> &[Self::Element] { - &self.deref()[..] - } -} - -impl AsMutSlice for Packet { - fn as_mut_slice(&mut self) -> &mut [Self::Element] { - &mut self.deref_mut()[..] - } -} - -impl Deref for Packet { - type Target = [u8; MTU]; - - fn deref(&self) -> &[u8; MTU] { - &self.0 - } -} - -impl DerefMut for Packet { - fn deref_mut(&mut self) -> &mut [u8; MTU] { - &mut self.0 - } -} - -pub struct PacketBuf { - packet: PacketBox, - range: Range, -} - -impl AsSlice for PacketBuf { - type Element = u8; - - fn as_slice(&self) -> &[Self::Element] { - &self.packet[self.range.clone()] - } -} - -impl AsMutSlice for PacketBuf { - fn as_mut_slice(&mut self) -> &mut [Self::Element] { - &mut self.packet[self.range.clone()] - } -} - -impl Deref for PacketBuf { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - &self.packet[self.range.clone()] - } -} - -impl DerefMut for PacketBuf { - fn deref_mut(&mut self) -> &mut [u8] { - &mut self.packet[self.range.clone()] - } -} diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs deleted file mode 100644 index 3a7610758..000000000 --- a/embassy-net/src/stack.rs +++ /dev/null @@ -1,315 +0,0 @@ -use core::cell::UnsafeCell; -use core::future::{poll_fn, Future}; -use core::task::{Context, Poll}; - -use embassy_sync::waitqueue::WakerRegistration; -use embassy_time::{Instant, Timer}; -use futures::pin_mut; -use heapless::Vec; -#[cfg(feature = "dhcpv4")] -use smoltcp::iface::SocketHandle; -use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage}; -#[cfg(feature = "medium-ethernet")] -use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes}; -#[cfg(feature = "medium-ethernet")] -use smoltcp::phy::{Device as _, Medium}; -#[cfg(feature = "dhcpv4")] -use smoltcp::socket::dhcpv4; -use smoltcp::time::Instant as SmolInstant; -#[cfg(feature = "medium-ethernet")] -use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress}; -use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr}; - -use crate::device::{Device, DeviceAdapter, LinkState}; - -const LOCAL_PORT_MIN: u16 = 1025; -const LOCAL_PORT_MAX: u16 = 65535; - -pub struct StackResources { - addresses: [IpCidr; ADDR], - sockets: [SocketStorage<'static>; SOCK], - - #[cfg(feature = "medium-ethernet")] - routes: [Option<(IpCidr, Route)>; 1], - #[cfg(feature = "medium-ethernet")] - neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR], -} - -impl StackResources { - pub fn new() -> Self { - Self { - addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32); ADDR], - sockets: [SocketStorage::EMPTY; SOCK], - #[cfg(feature = "medium-ethernet")] - routes: [None; 1], - #[cfg(feature = "medium-ethernet")] - neighbor_cache: [None; NEIGHBOR], - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Config { - pub address: Ipv4Cidr, - pub gateway: Option, - pub dns_servers: Vec, -} - -pub enum ConfigStrategy { - Static(Config), - #[cfg(feature = "dhcpv4")] - Dhcp, -} - -pub struct Stack { - pub(crate) socket: UnsafeCell, - inner: UnsafeCell>, -} - -struct Inner { - device: DeviceAdapter, - link_up: bool, - config: Option, - #[cfg(feature = "dhcpv4")] - dhcp_socket: Option, -} - -pub(crate) struct SocketStack { - pub(crate) sockets: SocketSet<'static>, - pub(crate) iface: Interface<'static>, - pub(crate) waker: WakerRegistration, - next_local_port: u16, -} - -unsafe impl Send for Stack {} - -impl Stack { - pub fn new( - device: D, - config: ConfigStrategy, - resources: &'static mut StackResources, - random_seed: u64, - ) -> Self { - #[cfg(feature = "medium-ethernet")] - let medium = device.capabilities().medium; - - #[cfg(feature = "medium-ethernet")] - let ethernet_addr = if medium == Medium::Ethernet { - device.ethernet_address() - } else { - [0, 0, 0, 0, 0, 0] - }; - - let mut device = DeviceAdapter::new(device); - - let mut b = InterfaceBuilder::new(); - b = b.ip_addrs(&mut resources.addresses[..]); - b = b.random_seed(random_seed); - - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr))); - b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..])); - b = b.routes(Routes::new(&mut resources.routes[..])); - } - - let iface = b.finalize(&mut device); - - 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; - - let mut inner = Inner { - device, - link_up: false, - config: None, - #[cfg(feature = "dhcpv4")] - dhcp_socket: None, - }; - let mut socket = SocketStack { - sockets, - iface, - waker: WakerRegistration::new(), - next_local_port, - }; - - match config { - ConfigStrategy::Static(config) => inner.apply_config(&mut socket, config), - #[cfg(feature = "dhcpv4")] - ConfigStrategy::Dhcp => { - let handle = socket.sockets.add(smoltcp::socket::dhcpv4::Socket::new()); - inner.dhcp_socket = Some(handle); - } - } - - Self { - socket: UnsafeCell::new(socket), - inner: UnsafeCell::new(inner), - } - } - - /// SAFETY: must not call reentrantly. - unsafe fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { - f(&*self.socket.get(), &*self.inner.get()) - } - - /// SAFETY: must not call reentrantly. - unsafe fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { - f(&mut *self.socket.get(), &mut *self.inner.get()) - } - - pub fn ethernet_address(&self) -> [u8; 6] { - unsafe { self.with(|_s, i| i.device.device.ethernet_address()) } - } - - pub fn is_link_up(&self) -> bool { - unsafe { self.with(|_s, i| i.link_up) } - } - - pub fn is_config_up(&self) -> bool { - unsafe { self.with(|_s, i| i.config.is_some()) } - } - - pub fn config(&self) -> Option { - unsafe { self.with(|_s, i| i.config.clone()) } - } - - pub async fn run(&self) -> ! { - poll_fn(|cx| { - unsafe { self.with_mut(|s, i| i.poll(cx, s)) } - Poll::<()>::Pending - }) - .await; - unreachable!() - } -} - -impl SocketStack { - #[allow(clippy::absurd_extreme_comparisons)] - pub fn get_local_port(&mut self) -> u16 { - let res = self.next_local_port; - self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; - res - } -} - -impl Inner { - fn apply_config(&mut self, s: &mut SocketStack, config: Config) { - #[cfg(feature = "medium-ethernet")] - let medium = self.device.capabilities().medium; - - debug!("Acquired IP configuration:"); - - debug!(" IP address: {}", config.address); - self.set_ipv4_addr(s, config.address); - - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - if let Some(gateway) = config.gateway { - debug!(" Default gateway: {}", gateway); - s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap(); - } else { - debug!(" Default gateway: None"); - s.iface.routes_mut().remove_default_ipv4_route(); - } - } - for (i, s) in config.dns_servers.iter().enumerate() { - debug!(" DNS server {}: {}", i, s); - } - - self.config = Some(config) - } - - #[allow(unused)] // used only with dhcp - fn unapply_config(&mut self, s: &mut SocketStack) { - #[cfg(feature = "medium-ethernet")] - let medium = self.device.capabilities().medium; - - debug!("Lost IP configuration"); - self.set_ipv4_addr(s, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - s.iface.routes_mut().remove_default_ipv4_route(); - } - self.config = None - } - - fn set_ipv4_addr(&mut self, s: &mut SocketStack, cidr: Ipv4Cidr) { - s.iface.update_ip_addrs(|addrs| { - let dest = addrs.iter_mut().next().unwrap(); - *dest = IpCidr::Ipv4(cidr); - }); - } - - fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { - self.device.device.register_waker(cx.waker()); - s.waker.register(cx.waker()); - - let timestamp = instant_to_smoltcp(Instant::now()); - if s.iface.poll(timestamp, &mut self.device, &mut s.sockets).is_err() { - // If poll() returns error, it may not be done yet, so poll again later. - cx.waker().wake_by_ref(); - return; - } - - // Update link up - let old_link_up = self.link_up; - self.link_up = self.device.device.link_state() == LinkState::Up; - - // Print when changed - if old_link_up != self.link_up { - info!("link_up = {:?}", self.link_up); - } - - #[cfg(feature = "dhcpv4")] - if let Some(dhcp_handle) = self.dhcp_socket { - let socket = s.sockets.get_mut::(dhcp_handle); - - if self.link_up { - match socket.poll() { - None => {} - Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s), - Some(dhcpv4::Event::Configured(config)) => { - let mut dns_servers = Vec::new(); - for s in &config.dns_servers { - if let Some(addr) = s { - dns_servers.push(addr.clone()).unwrap(); - } - } - - self.apply_config( - s, - Config { - address: config.address, - gateway: config.router, - dns_servers, - }, - ) - } - } - } else if old_link_up { - socket.reset(); - self.unapply_config(s); - } - } - //if old_link_up || self.link_up { - // self.poll_configurator(timestamp) - //} - - if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { - let t = Timer::at(instant_from_smoltcp(poll_at)); - pin_mut!(t); - if t.poll(cx).is_ready() { - cx.waker().wake_by_ref(); - } - } - } -} - -fn instant_to_smoltcp(instant: Instant) -> SmolInstant { - SmolInstant::from_millis(instant.as_millis() as i64) -} - -fn instant_from_smoltcp(instant: SmolInstant) -> Instant { - Instant::from_millis(instant.total_millis() as u64) -} diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index f8fff3e2d..d46bd4dbf 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -1,16 +1,15 @@ -use core::cell::UnsafeCell; +use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::Poll; +use embassy_net_driver::Driver; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::tcp; use smoltcp::time::Duration; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; -use super::stack::Stack; -use crate::stack::SocketStack; -use crate::Device; +use crate::{SocketStack, Stack}; #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -67,9 +66,8 @@ impl<'a> TcpWriter<'a> { } impl<'a> TcpSocket<'a> { - pub fn new(stack: &'a Stack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { - // safety: not accessed reentrantly. - let s = unsafe { &mut *stack.socket.get() }; + 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( @@ -93,17 +91,18 @@ impl<'a> TcpSocket<'a> { where T: Into, { - // safety: not accessed reentrantly. - let local_port = unsafe { &mut *self.io.stack.get() }.get_local_port(); + let local_port = self.io.stack.borrow_mut().get_local_port(); - // safety: not accessed reentrantly. - match unsafe { self.io.with_mut(|s, i| s.connect(i, remote_endpoint, local_port)) } { + match { + self.io + .with_mut(|s, i| s.connect(i.context(), remote_endpoint, local_port)) + } { Ok(()) => {} Err(tcp::ConnectError::InvalidState) => return Err(ConnectError::InvalidState), Err(tcp::ConnectError::Unaddressable) => return Err(ConnectError::NoRoute), } - poll_fn(|cx| unsafe { + poll_fn(|cx| { self.io.with_mut(|s, _| match s.state() { tcp::State::Closed | tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)), tcp::State::Listen => unreachable!(), @@ -121,14 +120,13 @@ impl<'a> TcpSocket<'a> { where T: Into, { - // safety: not accessed reentrantly. - match unsafe { self.io.with_mut(|s, _| s.listen(local_endpoint)) } { + match self.io.with_mut(|s, _| s.listen(local_endpoint)) { Ok(()) => {} Err(tcp::ListenError::InvalidState) => return Err(AcceptError::InvalidState), Err(tcp::ListenError::Unaddressable) => return Err(AcceptError::InvalidPort), } - poll_fn(|cx| unsafe { + poll_fn(|cx| { self.io.with_mut(|s, _| match s.state() { tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => { s.register_send_waker(cx.waker()); @@ -149,51 +147,49 @@ impl<'a> TcpSocket<'a> { } pub fn set_timeout(&mut self, duration: Option) { - unsafe { self.io.with_mut(|s, _| s.set_timeout(duration)) } + self.io.with_mut(|s, _| s.set_timeout(duration)) } pub fn set_keep_alive(&mut self, interval: Option) { - unsafe { self.io.with_mut(|s, _| s.set_keep_alive(interval)) } + self.io.with_mut(|s, _| s.set_keep_alive(interval)) } pub fn set_hop_limit(&mut self, hop_limit: Option) { - unsafe { self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) } + self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) } pub fn local_endpoint(&self) -> Option { - unsafe { self.io.with(|s, _| s.local_endpoint()) } + self.io.with(|s, _| s.local_endpoint()) } pub fn remote_endpoint(&self) -> Option { - unsafe { self.io.with(|s, _| s.remote_endpoint()) } + self.io.with(|s, _| s.remote_endpoint()) } pub fn state(&self) -> tcp::State { - unsafe { self.io.with(|s, _| s.state()) } + self.io.with(|s, _| s.state()) } pub fn close(&mut self) { - unsafe { self.io.with_mut(|s, _| s.close()) } + self.io.with_mut(|s, _| s.close()) } pub fn abort(&mut self) { - unsafe { self.io.with_mut(|s, _| s.abort()) } + self.io.with_mut(|s, _| s.abort()) } pub fn may_send(&self) -> bool { - unsafe { self.io.with(|s, _| s.may_send()) } + self.io.with(|s, _| s.may_send()) } pub fn may_recv(&self) -> bool { - unsafe { self.io.with(|s, _| s.may_recv()) } + self.io.with(|s, _| s.may_recv()) } } impl<'a> Drop for TcpSocket<'a> { fn drop(&mut self) { - // safety: not accessed reentrantly. - let s = unsafe { &mut *self.io.stack.get() }; - s.sockets.remove(self.io.handle); + self.io.stack.borrow_mut().sockets.remove(self.io.handle); } } @@ -201,21 +197,19 @@ impl<'a> Drop for TcpSocket<'a> { #[derive(Copy, Clone)] struct TcpIo<'a> { - stack: &'a UnsafeCell, + stack: &'a RefCell, handle: SocketHandle, } impl<'d> TcpIo<'d> { - /// SAFETY: must not call reentrantly. - unsafe fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.get(); + 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) } - /// SAFETY: must not call reentrantly. - unsafe fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.get(); + 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(); @@ -223,7 +217,7 @@ impl<'d> TcpIo<'d> { } async fn read(&mut self, buf: &mut [u8]) -> Result { - poll_fn(move |cx| unsafe { + poll_fn(move |cx| { // CAUTION: smoltcp semantics around EOF are different to what you'd expect // from posix-like IO, so we have to tweak things here. self.with_mut(|s, _| match s.recv_slice(buf) { @@ -244,7 +238,7 @@ impl<'d> TcpIo<'d> { } async fn write(&mut self, buf: &[u8]) -> Result { - poll_fn(move |cx| unsafe { + poll_fn(move |cx| { self.with_mut(|s, _| match s.send_slice(buf) { // Not ready to send (no space in the tx buffer) Ok(0) => { @@ -271,8 +265,6 @@ impl<'d> TcpIo<'d> { #[cfg(feature = "nightly")] mod embedded_io_impls { - use core::future::Future; - use super::*; impl embedded_io::Error for ConnectError { @@ -292,30 +284,18 @@ mod embedded_io_impls { } impl<'d> embedded_io::asynch::Read for TcpSocket<'d> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.io.read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await } } impl<'d> embedded_io::asynch::Write for TcpSocket<'d> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.io.write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.io.flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await } } @@ -324,12 +304,8 @@ mod embedded_io_impls { } impl<'d> embedded_io::asynch::Read for TcpReader<'d> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.io.read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await } } @@ -338,27 +314,19 @@ mod embedded_io_impls { } impl<'d> embedded_io::asynch::Write for TcpWriter<'d> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.io.write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.io.flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await } } } #[cfg(all(feature = "unstable-traits", feature = "nightly"))] pub mod client { - use core::future::Future; + use core::cell::UnsafeCell; use core::mem::MaybeUninit; use core::ptr::NonNull; @@ -368,45 +336,46 @@ pub mod client { use super::*; /// TCP client capable of creating up to N multiple connections with tx and rx buffers according to TX_SZ and RX_SZ. - pub struct TcpClient<'d, D: Device, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { stack: &'d Stack, state: &'d TcpClientState, } - impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { + impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { /// Create a new TcpClient pub fn new(stack: &'d Stack, state: &'d TcpClientState) -> Self { Self { stack, state } } } - impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + 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> { type Error = Error; type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; - type ConnectFuture<'m> = impl Future, Self::Error>> + 'm - where - Self: 'm; - fn connect<'m>(&'m self, remote: embedded_nal_async::SocketAddr) -> Self::ConnectFuture<'m> { - async move { - let addr: crate::IpAddress = match remote.ip() { - IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())), - #[cfg(feature = "proto-ipv6")] - IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())), - #[cfg(not(feature = "proto-ipv6"))] - IpAddr::V6(_) => panic!("ipv6 support not enabled"), - }; - let remote_endpoint = (addr, remote.port()); - let mut socket = TcpConnection::new(&self.stack, self.state)?; - socket - .socket - .connect(remote_endpoint) - .await - .map_err(|_| Error::ConnectionReset)?; - Ok(socket) - } + async fn connect<'a>( + &'a self, + remote: embedded_nal_async::SocketAddr, + ) -> Result, Self::Error> + where + Self: 'a, + { + let addr: crate::IpAddress = match remote.ip() { + IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())), + #[cfg(feature = "proto-ipv6")] + IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())), + #[cfg(not(feature = "proto-ipv6"))] + IpAddr::V6(_) => panic!("ipv6 support not enabled"), + }; + let remote_endpoint = (addr, remote.port()); + let mut socket = TcpConnection::new(&self.stack, self.state)?; + socket + .socket + .connect(remote_endpoint) + .await + .map_err(|_| Error::ConnectionReset)?; + Ok(socket) } } @@ -417,10 +386,10 @@ 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: &'d Stack, 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().0, &mut bufs.as_mut().1) }, + socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, state, bufs, }) @@ -445,32 +414,20 @@ pub mod client { impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Read for TcpConnection<'d, N, TX_SZ, RX_SZ> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.socket.read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.socket.read(buf).await } } impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Write for TcpConnection<'d, N, TX_SZ, RX_SZ> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.socket.write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.socket.write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.socket.flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.socket.flush().await } } diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index f2e33493c..0ee8c6e19 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -1,14 +1,14 @@ -use core::cell::UnsafeCell; +use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::Poll; +use embassy_net_driver::Driver; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::udp::{self, PacketMetadata}; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; -use super::stack::SocketStack; -use crate::{Device, Stack}; +use crate::{SocketStack, Stack}; #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -27,20 +27,19 @@ pub enum Error { } pub struct UdpSocket<'a> { - stack: &'a UnsafeCell, + stack: &'a RefCell, handle: SocketHandle, } impl<'a> UdpSocket<'a> { - pub fn new( + pub fn new( stack: &'a Stack, rx_meta: &'a mut [PacketMetadata], rx_buffer: &'a mut [u8], tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - // safety: not accessed reentrantly. - let s = unsafe { &mut *stack.socket.get() }; + let s = &mut *stack.socket.borrow_mut(); let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; @@ -63,30 +62,26 @@ impl<'a> UdpSocket<'a> { { let mut endpoint = endpoint.into(); - // safety: not accessed reentrantly. if endpoint.port == 0 { // If user didn't specify port allocate a dynamic port. - endpoint.port = unsafe { &mut *self.stack.get() }.get_local_port(); + endpoint.port = self.stack.borrow_mut().get_local_port(); } - // safety: not accessed reentrantly. - match unsafe { self.with_mut(|s, _| s.bind(endpoint)) } { + match self.with_mut(|s, _| s.bind(endpoint)) { Ok(()) => Ok(()), Err(udp::BindError::InvalidState) => Err(BindError::InvalidState), Err(udp::BindError::Unaddressable) => Err(BindError::NoRoute), } } - /// SAFETY: must not call reentrantly. - unsafe fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.get(); + 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) } - /// SAFETY: must not call reentrantly. - unsafe fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.get(); + 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(); @@ -94,7 +89,7 @@ impl<'a> UdpSocket<'a> { } pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> { - poll_fn(move |cx| unsafe { + poll_fn(move |cx| { self.with_mut(|s, _| match s.recv_slice(buf) { Ok(x) => Poll::Ready(Ok(x)), // No data ready @@ -113,7 +108,7 @@ impl<'a> UdpSocket<'a> { T: Into, { let remote_endpoint = remote_endpoint.into(); - poll_fn(move |cx| unsafe { + poll_fn(move |cx| { self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { // Entire datagram has been sent Ok(()) => Poll::Ready(Ok(())), @@ -128,30 +123,28 @@ impl<'a> UdpSocket<'a> { } pub fn endpoint(&self) -> IpListenEndpoint { - unsafe { self.with(|s, _| s.endpoint()) } + self.with(|s, _| s.endpoint()) } pub fn is_open(&self) -> bool { - unsafe { self.with(|s, _| s.is_open()) } + self.with(|s, _| s.is_open()) } pub fn close(&mut self) { - unsafe { self.with_mut(|s, _| s.close()) } + self.with_mut(|s, _| s.close()) } pub fn may_send(&self) -> bool { - unsafe { self.with(|s, _| s.can_send()) } + self.with(|s, _| s.can_send()) } pub fn may_recv(&self) -> bool { - unsafe { self.with(|s, _| s.can_recv()) } + self.with(|s, _| s.can_recv()) } } impl Drop for UdpSocket<'_> { fn drop(&mut self) { - // safety: not accessed reentrantly. - let s = unsafe { &mut *self.stack.get() }; - s.sockets.remove(self.handle); + self.stack.borrow_mut().sockets.remove(self.handle); } } diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 5459bc90c..6b06d5d05 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -75,8 +75,8 @@ embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optiona embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9", optional = true} -embedded-hal-async = { version = "=0.1.0-alpha.2", optional = true} -embedded-io = { version = "0.3.0", features = ["async"], optional = true } +embedded-hal-async = { version = "=0.2.0-alpha.0", optional = true} +embedded-io = { version = "0.4.0", features = ["async"], optional = true } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } @@ -90,13 +90,13 @@ embedded-storage = "0.3.0" embedded-storage-async = { version = "0.3.0", optional = true } cfg-if = "1.0.0" -nrf52805-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52810-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52811-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52820-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52832-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52833-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52840-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf5340-app-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf5340-net-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf9160-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } +nrf52805-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf52810-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf52811-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf52820-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf52832-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf52833-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf52840-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf5340-app-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf5340-net-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf9160-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md new file mode 100644 index 000000000..a31cfae68 --- /dev/null +++ b/embassy-nrf/README.md @@ -0,0 +1,58 @@ +# Embassy nRF HAL + +HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. + +The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs +for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to +complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. + +## EasyDMA considerations + +On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting +with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI). +However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust +slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen: + +```no_run +// As we pass a slice to the function whose contents will not ever change, +// the compiler writes it into the flash and thus the pointer to it will +// reference static memory. Since EasyDMA requires slices to reside in RAM, +// this function call will fail. +let result = spim.write_from_ram(&[1, 2, 3]); +assert_eq!(result, Err(Error::BufferNotInRAM)); + +// The data is still static and located in flash. However, since we are assigning +// it to a variable, the compiler will load it into memory. Passing a reference to the +// variable will yield a pointer that references dynamic memory, thus making EasyDMA happy. +// This function call succeeds. +let data = [1, 2, 3]; +let result = spim.write_from_ram(&data); +assert!(result.is_ok()); +``` + +Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions: +- Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM. +- Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission. + +Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will +fail and notify you, or the more convenient versions without the suffix which are potentially a little bit +more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage +as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840). + +Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as +mutable slices always reside in RAM. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 6e85a159f..112f084c1 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -1,4 +1,4 @@ -//! Async buffered UART +//! Async buffered UART driver. //! //! WARNING!!! The functionality provided here is intended to be used only //! in situations where hardware flow control are available i.e. CTS and RTS. @@ -15,7 +15,7 @@ use core::cell::RefCell; use core::cmp::min; -use core::future::{poll_fn, Future}; +use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -69,7 +69,7 @@ struct StateInner<'d, U: UarteInstance, T: TimerInstance> { tx_waker: WakerRegistration, } -/// Interface to a UARTE instance +/// Buffered UARTE driver. pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { inner: RefCell>>, } @@ -199,6 +199,9 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { }); } + /// Split the UART in reader and writer parts. + /// + /// This allows reading and writing concurrently from independent tasks. pub fn split<'u>(&'u mut self) -> (BufferedUarteRx<'u, 'd, U, T>, BufferedUarteTx<'u, 'd, U, T>) { (BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self }) } @@ -320,10 +323,12 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { } } +/// Reader part of the buffered UARTE driver. pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> { inner: &'u BufferedUarte<'d, U, T>, } +/// Writer part of the buffered UARTE driver. pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> { inner: &'u BufferedUarte<'d, U, T>, } @@ -341,32 +346,20 @@ impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUar } impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.inner_read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner_read(buf).await } } impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarteRx<'u, 'd, U, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.inner.inner_read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.inner_read(buf).await } } impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; - - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - self.inner_fill_buf() + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner_fill_buf().await } fn consume(&mut self, amt: usize) { @@ -375,12 +368,8 @@ impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for Bu } impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarteRx<'u, 'd, U, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; - - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - self.inner.inner_fill_buf() + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.inner_fill_buf().await } fn consume(&mut self, amt: usize) { @@ -389,38 +378,22 @@ impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRea } impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.inner_write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner_write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.inner_flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner_flush().await } } impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarteTx<'u, 'd, U, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.inner.inner_write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner.inner_write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.inner.inner_flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.inner_flush().await } } diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index d078fa0ad..bf4019c13 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -131,8 +131,12 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); +impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0); + impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); +impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); @@ -193,8 +197,8 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index faa52d8fb..6c28a3bea 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -128,14 +128,21 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // PDM + PDM, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); +impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0); + impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); +impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0); + impl_pwm!(PWM0, PWM0, PWM0); impl_timer!(TIMER0, TIMER0, TIMER0); @@ -208,14 +215,14 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index bbdf1cbe5..e7214cf5c 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -128,6 +128,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // PDM + PDM, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -135,8 +138,13 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(TWISPI0, SPIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); impl_spim!(SPI1, SPIM1, SPIM1_SPIS1_SPI1); +impl_spis!(TWISPI0, SPIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); +impl_spis!(SPI1, SPIS1, SPIM1_SPIS1_SPI1); + impl_twim!(TWISPI0, TWIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); +impl_twis!(TWISPI0, TWIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); + impl_pwm!(PWM0, PWM0, PWM0); impl_timer!(TIMER0, TIMER0, TIMER0); @@ -209,14 +217,14 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index dba033b0f..21d1d16cc 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -136,9 +136,15 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 18b8eda67..152dad4e3 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -138,6 +138,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // I2S + I2S, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -146,9 +149,16 @@ impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); @@ -225,14 +235,16 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); + +impl_i2s!(I2S, I2S, I2S); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; @@ -274,6 +286,6 @@ pub mod irqs { declare!(PWM2); declare!(SPIM2_SPIS2_SPI2); declare!(RTC2); - declare!(I2S); declare!(FPU); + declare!(I2S); } diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 39a0f93f9..a99ca6343 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -158,6 +158,12 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // PDM + PDM, + + // I2S + I2S, } #[cfg(feature = "nightly")] @@ -171,9 +177,16 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI3, SPIM3, SPIM3); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); @@ -268,14 +281,16 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); + +impl_i2s!(I2S, I2S, I2S); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; @@ -317,10 +332,10 @@ pub mod irqs { declare!(PWM2); declare!(SPIM2_SPIS2_SPI2); declare!(RTC2); - declare!(I2S); declare!(FPU); declare!(USBD); declare!(UARTE1); declare!(PWM3); declare!(SPIM3); + declare!(I2S); } diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index e3d8f34a1..4f7463be2 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -161,6 +161,12 @@ embassy_hal_common::peripherals! { // TEMP TEMP, + + // PDM + PDM, + + // I2S + I2S, } #[cfg(feature = "nightly")] @@ -174,9 +180,16 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI3, SPIM3, SPIM3); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); @@ -273,14 +286,16 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); + +impl_i2s!(I2S, I2S, I2S); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; @@ -322,7 +337,6 @@ pub mod irqs { declare!(PWM2); declare!(SPIM2_SPIS2_SPI2); declare!(RTC2); - declare!(I2S); declare!(FPU); declare!(USBD); declare!(UARTE1); @@ -330,4 +344,5 @@ pub mod irqs { declare!(CRYPTOCELL); declare!(PWM3); declare!(SPIM3); + declare!(I2S); } diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index edf800ef3..c600fcbf6 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -1,3 +1,4 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { @@ -213,6 +214,8 @@ pub mod pac { pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; +pub const FLASH_SIZE: usize = 1024 * 1024; + embassy_hal_common::peripherals! { // USB USBD, @@ -224,6 +227,9 @@ embassy_hal_common::peripherals! { // WDT WDT, + // NVMC + NVMC, + // UARTE, TWI & SPI UARTETWISPI0, UARTETWISPI1, @@ -361,11 +367,21 @@ impl_spim!(UARTETWISPI1, SPIM1, SERIAL1); impl_spim!(UARTETWISPI2, SPIM2, SERIAL2); impl_spim!(UARTETWISPI3, SPIM3, SERIAL3); +impl_spis!(UARTETWISPI0, SPIS0, SERIAL0); +impl_spis!(UARTETWISPI1, SPIS1, SERIAL1); +impl_spis!(UARTETWISPI2, SPIS2, SERIAL2); +impl_spis!(UARTETWISPI3, SPIS3, SERIAL3); + impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); impl_twim!(UARTETWISPI1, TWIM1, SERIAL1); impl_twim!(UARTETWISPI2, TWIM2, SERIAL2); impl_twim!(UARTETWISPI3, TWIM3, SERIAL3); +impl_twis!(UARTETWISPI0, TWIS0, SERIAL0); +impl_twis!(UARTETWISPI1, TWIS1, SERIAL1); +impl_twis!(UARTETWISPI2, TWIS2, SERIAL2); +impl_twis!(UARTETWISPI3, TWIS3, SERIAL3); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); @@ -458,14 +474,14 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable); impl_ppi_channel!(PPI_CH30, 30 => configurable); impl_ppi_channel!(PPI_CH31, 31 => configurable); -impl_saadc_input!(P0_13, ANALOGINPUT0); -impl_saadc_input!(P0_14, ANALOGINPUT1); -impl_saadc_input!(P0_15, ANALOGINPUT2); -impl_saadc_input!(P0_16, ANALOGINPUT3); -impl_saadc_input!(P0_17, ANALOGINPUT4); -impl_saadc_input!(P0_18, ANALOGINPUT5); -impl_saadc_input!(P0_19, ANALOGINPUT6); -impl_saadc_input!(P0_20, ANALOGINPUT7); +impl_saadc_input!(P0_13, ANALOG_INPUT0); +impl_saadc_input!(P0_14, ANALOG_INPUT1); +impl_saadc_input!(P0_15, ANALOG_INPUT2); +impl_saadc_input!(P0_16, ANALOG_INPUT3); +impl_saadc_input!(P0_17, ANALOG_INPUT4); +impl_saadc_input!(P0_18, ANALOG_INPUT5); +impl_saadc_input!(P0_19, ANALOG_INPUT6); +impl_saadc_input!(P0_20, ANALOG_INPUT7); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index ae136e09d..a46583eca 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -1,3 +1,4 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { @@ -104,6 +105,8 @@ pub mod pac { pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; +pub const FLASH_SIZE: usize = 256 * 1024; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -112,6 +115,9 @@ embassy_hal_common::peripherals! { // WDT WDT, + // NVMC + NVMC, + // UARTE, TWI & SPI UARTETWISPI0, UARTETWISPI1, @@ -238,7 +244,9 @@ embassy_hal_common::peripherals! { impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); +impl_spis!(UARTETWISPI0, SPIS0, SERIAL0); impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); +impl_twis!(UARTETWISPI0, TWIS0, SERIAL0); impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); diff --git a/embassy-nrf/src/chips/nrf9160.rs b/embassy-nrf/src/chips/nrf9160.rs index a4be8564e..e1509ddde 100644 --- a/embassy-nrf/src/chips/nrf9160.rs +++ b/embassy-nrf/src/chips/nrf9160.rs @@ -1,3 +1,4 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { @@ -164,6 +165,8 @@ pub mod pac { pub const EASY_DMA_SIZE: usize = (1 << 13) - 1; pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; +pub const FLASH_SIZE: usize = 1024 * 1024; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -172,6 +175,9 @@ embassy_hal_common::peripherals! { // WDT WDT, + // NVMC + NVMC, + // UARTE, TWI & SPI UARTETWISPI0, UARTETWISPI1, @@ -260,6 +266,9 @@ embassy_hal_common::peripherals! { P0_29, P0_30, P0_31, + + // PDM + PDM, } impl_uarte!(UARTETWISPI0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); @@ -272,11 +281,21 @@ impl_spim!(UARTETWISPI1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_spim!(UARTETWISPI2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_spim!(UARTETWISPI3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_spis!(UARTETWISPI0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_spis!(UARTETWISPI1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_spis!(UARTETWISPI2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_spis!(UARTETWISPI3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); + impl_twim!(UARTETWISPI0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); impl_twim!(UARTETWISPI1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_twim!(UARTETWISPI2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_twim!(UARTETWISPI3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_twis!(UARTETWISPI0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_twis!(UARTETWISPI1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_twis!(UARTETWISPI2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_twis!(UARTETWISPI3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); @@ -336,14 +355,14 @@ impl_ppi_channel!(PPI_CH13, 13 => configurable); impl_ppi_channel!(PPI_CH14, 14 => configurable); impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_saadc_input!(P0_13, ANALOGINPUT0); -impl_saadc_input!(P0_14, ANALOGINPUT1); -impl_saadc_input!(P0_15, ANALOGINPUT2); -impl_saadc_input!(P0_16, ANALOGINPUT3); -impl_saadc_input!(P0_17, ANALOGINPUT4); -impl_saadc_input!(P0_18, ANALOGINPUT5); -impl_saadc_input!(P0_19, ANALOGINPUT6); -impl_saadc_input!(P0_20, ANALOGINPUT7); +impl_saadc_input!(P0_13, ANALOG_INPUT0); +impl_saadc_input!(P0_14, ANALOG_INPUT1); +impl_saadc_input!(P0_15, ANALOG_INPUT2); +impl_saadc_input!(P0_16, ANALOG_INPUT3); +impl_saadc_input!(P0_17, ANALOG_INPUT4); +impl_saadc_input!(P0_18, ANALOG_INPUT5); +impl_saadc_input!(P0_19, ANALOG_INPUT6); +impl_saadc_input!(P0_20, ANALOG_INPUT7); pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index bb64e41e9..895ab9340 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -1,4 +1,4 @@ -//! General purpose input/output for nRF. +//! General purpose input/output (GPIO) driver. #![macro_use] use core::convert::Infallible; @@ -70,7 +70,7 @@ impl<'d, T: Pin> Input<'d, T> { } /// Digital input or output level. -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Level { /// Logical low. @@ -88,9 +88,9 @@ impl From for Level { } } -impl Into for Level { - fn into(self) -> bool { - match self { +impl From for bool { + fn from(level: Level) -> bool { + match level { Level::Low => false, Level::High => true, } diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index 25ad90496..e1816eb9b 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -1,8 +1,10 @@ +//! GPIO task/event (GPIOTE) driver. + use core::convert::Infallible; use core::future::{poll_fn, Future}; use core::task::{Context, Poll}; -use embassy_hal_common::{impl_peripheral, Peripheral, PeripheralRef}; +use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use crate::gpio::sealed::Pin as _; @@ -11,29 +13,38 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::ppi::{Event, Task}; use crate::{interrupt, pac, peripherals}; -pub const CHANNEL_COUNT: usize = 8; +/// Amount of GPIOTE channels in the chip. +const CHANNEL_COUNT: usize = 8; #[cfg(any(feature = "nrf52833", feature = "nrf52840"))] -pub const PIN_COUNT: usize = 48; +const PIN_COUNT: usize = 48; #[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))] -pub const PIN_COUNT: usize = 32; +const PIN_COUNT: usize = 32; #[allow(clippy::declare_interior_mutable_const)] const NEW_AW: AtomicWaker = AtomicWaker::new(); static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; +/// Polarity for listening to events for GPIOTE input channels. pub enum InputChannelPolarity { + /// Don't listen for any pin changes. None, + /// Listen for high to low changes. HiToLo, + /// Listen for low to high changes. LoToHi, + /// Listen for any change, either low to high or high to low. Toggle, } -/// Polarity of the `task out` operation. +/// Polarity of the OUT task operation for GPIOTE output channels. pub enum OutputChannelPolarity { + /// Set the pin high. Set, + /// Set the pin low. Clear, + /// Toggle the pin. Toggle, } @@ -148,7 +159,7 @@ impl Iterator for BitIter { /// GPIOTE channel driver in input mode pub struct InputChannel<'d, C: Channel, T: GpioPin> { - ch: C, + ch: PeripheralRef<'d, C>, pin: Input<'d, T>, } @@ -162,7 +173,10 @@ impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> { } impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { - pub fn new(ch: C, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { + /// Create a new GPIOTE input channel driver. + pub fn new(ch: impl Peripheral

+ 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { + into_ref!(ch); + let g = regs(); let num = ch.number(); @@ -186,6 +200,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { InputChannel { ch, pin } } + /// Asynchronously wait for an event in this channel. pub async fn wait(&self) { let g = regs(); let num = self.ch.number(); @@ -215,7 +230,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { /// GPIOTE channel driver in output mode pub struct OutputChannel<'d, C: Channel, T: GpioPin> { - ch: C, + ch: PeripheralRef<'d, C>, _pin: Output<'d, T>, } @@ -229,7 +244,9 @@ impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> { } impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { - pub fn new(ch: C, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { + /// Create a new GPIOTE output channel driver. + pub fn new(ch: impl Peripheral

+ 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { + into_ref!(ch); let g = regs(); let num = ch.number(); @@ -255,20 +272,20 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { OutputChannel { ch, _pin: pin } } - /// Triggers `task out` (as configured with task_out_polarity, defaults to Toggle). + /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle). pub fn out(&self) { let g = regs(); g.tasks_out[self.ch.number()].write(|w| unsafe { w.bits(1) }); } - /// Triggers `task set` (set associated pin high). + /// Triggers the SET task (set associated pin high). #[cfg(not(feature = "nrf51"))] pub fn set(&self) { let g = regs(); g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) }); } - /// Triggers `task clear` (set associated pin low). + /// Triggers the CLEAR task (set associated pin low). #[cfg(not(feature = "nrf51"))] pub fn clear(&self) { let g = regs(); @@ -333,48 +350,58 @@ impl<'a> Future for PortInputFuture<'a> { } impl<'d, T: GpioPin> Input<'d, T> { + /// Wait until the pin is high. If it is already high, return immediately. pub async fn wait_for_high(&mut self) { self.pin.wait_for_high().await } + /// Wait until the pin is low. If it is already low, return immediately. pub async fn wait_for_low(&mut self) { self.pin.wait_for_low().await } + /// Wait for the pin to undergo a transition from low to high. pub async fn wait_for_rising_edge(&mut self) { self.pin.wait_for_rising_edge().await } + /// Wait for the pin to undergo a transition from high to low. pub async fn wait_for_falling_edge(&mut self) { self.pin.wait_for_falling_edge().await } + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. pub async fn wait_for_any_edge(&mut self) { self.pin.wait_for_any_edge().await } } impl<'d, T: GpioPin> Flex<'d, T> { + /// Wait until the pin is high. If it is already high, return immediately. pub async fn wait_for_high(&mut self) { self.pin.conf().modify(|_, w| w.sense().high()); PortInputFuture::new(&mut self.pin).await } + /// Wait until the pin is low. If it is already low, return immediately. pub async fn wait_for_low(&mut self) { self.pin.conf().modify(|_, w| w.sense().low()); PortInputFuture::new(&mut self.pin).await } + /// Wait for the pin to undergo a transition from low to high. pub async fn wait_for_rising_edge(&mut self) { self.wait_for_low().await; self.wait_for_high().await; } + /// Wait for the pin to undergo a transition from high to low. pub async fn wait_for_falling_edge(&mut self) { self.wait_for_high().await; self.wait_for_low().await; } + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. pub async fn wait_for_any_edge(&mut self) { if self.is_high() { self.pin.conf().modify(|_, w| w.sense().low()); @@ -391,8 +418,17 @@ mod sealed { pub trait Channel {} } +/// GPIOTE channel trait. +/// +/// Implemented by all GPIOTE channels. pub trait Channel: sealed::Channel + Sized { + /// Get the channel number. fn number(&self) -> usize; + + /// Convert this channel to a type-erased `AnyChannel`. + /// + /// This allows using several channels in situations that might require + /// them to be the same type, like putting them in an array. fn degrade(self) -> AnyChannel { AnyChannel { number: self.number() as u8, @@ -400,6 +436,12 @@ pub trait Channel: sealed::Channel + Sized { } } +/// Type-erased channel. +/// +/// Obtained by calling `Channel::degrade`. +/// +/// This allows using several channels in situations that might require +/// them to be the same type, like putting them in an array. pub struct AnyChannel { number: u8, } @@ -470,71 +512,49 @@ mod eh1 { #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eha { - use futures::FutureExt; - use super::*; impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Input<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_high().await) } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_low().await) } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_rising_edge().await) } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_falling_edge().await) } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_any_edge().await) } } impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Flex<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_high().await) } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_low().await) } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_rising_edge().await) } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_falling_edge().await) } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_any_edge().await) } } } diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs new file mode 100644 index 000000000..770df7c89 --- /dev/null +++ b/embassy-nrf/src/i2s.rs @@ -0,0 +1,1192 @@ +//! Inter-IC Sound (I2S) driver. + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem::size_of; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_cortex_m::interrupt::InterruptExt; +use embassy_hal_common::drop::OnDrop; +use embassy_hal_common::{into_ref, PeripheralRef}; + +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::Interrupt; +use crate::pac::i2s::RegisterBlock; +use crate::util::{slice_in_ram_or, slice_ptr_parts}; +use crate::{Peripheral, EASY_DMA_SIZE}; + +/// Type alias for `MultiBuffering` with 2 buffers. +pub type DoubleBuffering = MultiBuffering; + +/// I2S transfer error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// The buffer is too long. + BufferTooLong, + /// The buffer is empty. + BufferZeroLength, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// The buffer address is not aligned. + BufferMisaligned, + /// The buffer length is not a multiple of the alignment. + BufferLengthMisaligned, +} + +/// I2S configuration. +#[derive(Clone)] +#[non_exhaustive] +pub struct Config { + /// Sample width + pub sample_width: SampleWidth, + /// Alignment + pub align: Align, + /// Sample format + pub format: Format, + /// Channel configuration. + pub channels: Channels, +} + +impl Default for Config { + fn default() -> Self { + Self { + sample_width: SampleWidth::_16bit, + align: Align::Left, + format: Format::I2S, + channels: Channels::Stereo, + } + } +} + +/// I2S clock configuration. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct MasterClock { + freq: MckFreq, + ratio: Ratio, +} + +impl MasterClock { + /// Create a new `MasterClock`. + pub fn new(freq: MckFreq, ratio: Ratio) -> Self { + Self { freq, ratio } + } +} + +impl MasterClock { + /// Get the sample rate for this clock configuration. + pub fn sample_rate(&self) -> u32 { + self.freq.to_frequency() / self.ratio.to_divisor() + } +} + +/// Master clock generator frequency. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum MckFreq { + /// 32 Mhz / 8 = 4000.00 kHz + _32MDiv8, + /// 32 Mhz / 10 = 3200.00 kHz + _32MDiv10, + /// 32 Mhz / 11 = 2909.09 kHz + _32MDiv11, + /// 32 Mhz / 15 = 2133.33 kHz + _32MDiv15, + /// 32 Mhz / 16 = 2000.00 kHz + _32MDiv16, + /// 32 Mhz / 21 = 1523.81 kHz + _32MDiv21, + /// 32 Mhz / 23 = 1391.30 kHz + _32MDiv23, + /// 32 Mhz / 30 = 1066.67 kHz + _32MDiv30, + /// 32 Mhz / 31 = 1032.26 kHz + _32MDiv31, + /// 32 Mhz / 32 = 1000.00 kHz + _32MDiv32, + /// 32 Mhz / 42 = 761.90 kHz + _32MDiv42, + /// 32 Mhz / 63 = 507.94 kHz + _32MDiv63, + /// 32 Mhz / 125 = 256.00 kHz + _32MDiv125, +} + +impl MckFreq { + const REGISTER_VALUES: &'static [u32] = &[ + 0x20000000, 0x18000000, 0x16000000, 0x11000000, 0x10000000, 0x0C000000, 0x0B000000, 0x08800000, 0x08400000, + 0x08000000, 0x06000000, 0x04100000, 0x020C0000, + ]; + + const FREQUENCIES: &'static [u32] = &[ + 4000000, 3200000, 2909090, 2133333, 2000000, 1523809, 1391304, 1066666, 1032258, 1000000, 761904, 507936, + 256000, + ]; + + /// Return the value that needs to be written to the register. + pub fn to_register_value(&self) -> u32 { + Self::REGISTER_VALUES[usize::from(*self)] + } + + /// Return the master clock frequency. + pub fn to_frequency(&self) -> u32 { + Self::FREQUENCIES[usize::from(*self)] + } +} + +impl From for usize { + fn from(variant: MckFreq) -> Self { + variant as _ + } +} + +/// Master clock frequency ratio +/// +/// Sample Rate = LRCK = MCK / Ratio +/// +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Ratio { + /// Divide by 32 + _32x, + /// Divide by 48 + _48x, + /// Divide by 64 + _64x, + /// Divide by 96 + _96x, + /// Divide by 128 + _128x, + /// Divide by 192 + _192x, + /// Divide by 256 + _256x, + /// Divide by 384 + _384x, + /// Divide by 512 + _512x, +} + +impl Ratio { + const RATIOS: &'static [u32] = &[32, 48, 64, 96, 128, 192, 256, 384, 512]; + + /// Return the value that needs to be written to the register. + pub fn to_register_value(&self) -> u8 { + usize::from(*self) as u8 + } + + /// Return the divisor for this ratio + pub fn to_divisor(&self) -> u32 { + Self::RATIOS[usize::from(*self)] + } +} + +impl From for usize { + fn from(variant: Ratio) -> Self { + variant as _ + } +} + +/// Approximate sample rates. +/// +/// Those are common sample rates that can not be configured without an small error. +/// +/// For custom master clock configuration, please refer to [MasterClock]. +#[derive(Clone, Copy)] +pub enum ApproxSampleRate { + /// 11025 Hz + _11025, + /// 16000 Hz + _16000, + /// 22050 Hz + _22050, + /// 32000 Hz + _32000, + /// 44100 Hz + _44100, + /// 48000 Hz + _48000, +} + +impl From for MasterClock { + fn from(value: ApproxSampleRate) -> Self { + match value { + // error = 86 + ApproxSampleRate::_11025 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_192x), + // error = 127 + ApproxSampleRate::_16000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_96x), + // error = 172 + ApproxSampleRate::_22050 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_96x), + // error = 254 + ApproxSampleRate::_32000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_48x), + // error = 344 + ApproxSampleRate::_44100 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_48x), + // error = 381 + ApproxSampleRate::_48000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_32x), + } + } +} + +impl ApproxSampleRate { + /// Get the sample rate as an integer. + pub fn sample_rate(&self) -> u32 { + MasterClock::from(*self).sample_rate() + } +} + +/// Exact sample rates. +/// +/// Those are non standard sample rates that can be configured without error. +/// +/// For custom master clock configuration, please refer to [Mode]. +#[derive(Clone, Copy)] +pub enum ExactSampleRate { + /// 8000 Hz + _8000, + /// 10582 Hz + _10582, + /// 12500 Hz + _12500, + /// 15625 Hz + _15625, + /// 15873 Hz + _15873, + /// 25000 Hz + _25000, + /// 31250 Hz + _31250, + /// 50000 Hz + _50000, + /// 62500 Hz + _62500, + /// 100000 Hz + _100000, + /// 125000 Hz + _125000, +} + +impl ExactSampleRate { + /// Get the sample rate as an integer. + pub fn sample_rate(&self) -> u32 { + MasterClock::from(*self).sample_rate() + } +} + +impl From for MasterClock { + fn from(value: ExactSampleRate) -> Self { + match value { + ExactSampleRate::_8000 => MasterClock::new(MckFreq::_32MDiv125, Ratio::_32x), + ExactSampleRate::_10582 => MasterClock::new(MckFreq::_32MDiv63, Ratio::_48x), + ExactSampleRate::_12500 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_256x), + ExactSampleRate::_15625 => MasterClock::new(MckFreq::_32MDiv32, Ratio::_64x), + ExactSampleRate::_15873 => MasterClock::new(MckFreq::_32MDiv63, Ratio::_32x), + ExactSampleRate::_25000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_128x), + ExactSampleRate::_31250 => MasterClock::new(MckFreq::_32MDiv32, Ratio::_32x), + ExactSampleRate::_50000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_64x), + ExactSampleRate::_62500 => MasterClock::new(MckFreq::_32MDiv16, Ratio::_32x), + ExactSampleRate::_100000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_32x), + ExactSampleRate::_125000 => MasterClock::new(MckFreq::_32MDiv8, Ratio::_32x), + } + } +} + +/// Sample width. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SampleWidth { + /// 8 bit samples. + _8bit, + /// 16 bit samples. + _16bit, + /// 24 bit samples. + _24bit, +} + +impl From for u8 { + fn from(variant: SampleWidth) -> Self { + variant as _ + } +} + +/// Channel used for the most significant sample value in a frame. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Align { + /// Left-align samples. + Left, + /// Right-align samples. + Right, +} + +impl From for bool { + fn from(variant: Align) -> Self { + match variant { + Align::Left => false, + Align::Right => true, + } + } +} + +/// Frame format. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Format { + /// I2S frame format + I2S, + /// Aligned frame format + Aligned, +} + +impl From for bool { + fn from(variant: Format) -> Self { + match variant { + Format::I2S => false, + Format::Aligned => true, + } + } +} + +/// Channels +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Channels { + /// Stereo (2 channels). + Stereo, + /// Mono, left channel only. + MonoLeft, + /// Mono, right channel only. + MonoRight, +} + +impl From for u8 { + fn from(variant: Channels) -> Self { + variant as _ + } +} + +/// I2S driver. +pub struct I2S<'d, T: Instance> { + i2s: PeripheralRef<'d, T>, + irq: PeripheralRef<'d, T::Interrupt>, + mck: Option>, + sck: PeripheralRef<'d, AnyPin>, + lrck: PeripheralRef<'d, AnyPin>, + sdin: Option>, + sdout: Option>, + master_clock: Option, + config: Config, +} + +impl<'d, T: Instance> I2S<'d, T> { + /// Create a new I2S in master mode + pub fn master( + i2s: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + mck: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

+ 'd, + master_clock: MasterClock, + config: Config, + ) -> Self { + into_ref!(i2s, irq, mck, sck, lrck); + Self { + i2s, + irq, + mck: Some(mck.map_into()), + sck: sck.map_into(), + lrck: lrck.map_into(), + sdin: None, + sdout: None, + master_clock: Some(master_clock), + config, + } + } + + /// Create a new I2S in slave mode + pub fn slave( + i2s: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(i2s, irq, sck, lrck); + Self { + i2s, + irq, + mck: None, + sck: sck.map_into(), + lrck: lrck.map_into(), + sdin: None, + sdout: None, + master_clock: None, + config, + } + } + + /// I2S output only + pub fn output( + mut self, + sdout: impl Peripheral

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

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

+ 'd, + sdout: impl Peripheral

+ 'd, + buffers_out: MultiBuffering, + buffers_in: MultiBuffering, + ) -> FullDuplexStream<'d, T, S, NB, NS> { + self.sdout = Some(sdout.into_ref().map_into()); + self.sdin = Some(sdin.into_ref().map_into()); + + FullDuplexStream { + _p: self.build(), + buffers_out, + buffers_in, + } + } + + fn build(self) -> PeripheralRef<'d, T> { + self.apply_config(); + self.select_pins(); + self.setup_interrupt(); + + let device = Device::::new(); + device.enable(); + + self.i2s + } + + fn apply_config(&self) { + let c = &T::regs().config; + match &self.master_clock { + Some(MasterClock { freq, ratio }) => { + c.mode.write(|w| w.mode().master()); + c.mcken.write(|w| w.mcken().enabled()); + c.mckfreq + .write(|w| unsafe { w.mckfreq().bits(freq.to_register_value()) }); + c.ratio.write(|w| unsafe { w.ratio().bits(ratio.to_register_value()) }); + } + None => { + c.mode.write(|w| w.mode().slave()); + } + }; + + c.swidth + .write(|w| unsafe { w.swidth().bits(self.config.sample_width.into()) }); + c.align.write(|w| w.align().bit(self.config.align.into())); + c.format.write(|w| w.format().bit(self.config.format.into())); + c.channels + .write(|w| unsafe { w.channels().bits(self.config.channels.into()) }); + } + + fn select_pins(&self) { + let psel = &T::regs().psel; + + if let Some(mck) = &self.mck { + psel.mck.write(|w| { + unsafe { w.bits(mck.psel_bits()) }; + w.connect().connected() + }); + } + + psel.sck.write(|w| { + unsafe { w.bits(self.sck.psel_bits()) }; + w.connect().connected() + }); + + psel.lrck.write(|w| { + unsafe { w.bits(self.lrck.psel_bits()) }; + w.connect().connected() + }); + + if let Some(sdin) = &self.sdin { + psel.sdin.write(|w| { + unsafe { w.bits(sdin.psel_bits()) }; + w.connect().connected() + }); + } + + if let Some(sdout) = &self.sdout { + psel.sdout.write(|w| { + unsafe { w.bits(sdout.psel_bits()) }; + w.connect().connected() + }); + } + } + + fn setup_interrupt(&self) { + self.irq.set_handler(Self::on_interrupt); + self.irq.unpend(); + self.irq.enable(); + + let device = Device::::new(); + device.disable_tx_ptr_interrupt(); + device.disable_rx_ptr_interrupt(); + device.disable_stopped_interrupt(); + + device.reset_tx_ptr_event(); + device.reset_rx_ptr_event(); + device.reset_stopped_event(); + + device.enable_tx_ptr_interrupt(); + device.enable_rx_ptr_interrupt(); + device.enable_stopped_interrupt(); + } + + fn on_interrupt(_: *mut ()) { + let device = Device::::new(); + let s = T::state(); + + if device.is_tx_ptr_updated() { + trace!("TX INT"); + s.tx_waker.wake(); + device.disable_tx_ptr_interrupt(); + } + + if device.is_rx_ptr_updated() { + trace!("RX INT"); + s.rx_waker.wake(); + device.disable_rx_ptr_interrupt(); + } + + if device.is_stopped() { + trace!("STOPPED INT"); + s.stop_waker.wake(); + device.disable_stopped_interrupt(); + } + } + + async fn stop() { + compiler_fence(Ordering::SeqCst); + + let device = Device::::new(); + device.stop(); + + T::state().started.store(false, Ordering::Relaxed); + + poll_fn(|cx| { + T::state().stop_waker.register(cx.waker()); + + if device.is_stopped() { + trace!("STOP: Ready"); + device.reset_stopped_event(); + Poll::Ready(()) + } else { + trace!("STOP: Pending"); + Poll::Pending + } + }) + .await; + + device.disable(); + } + + async fn send_from_ram(buffer_ptr: *const [S]) -> Result<(), Error> + where + S: Sample, + { + trace!("SEND: {}", buffer_ptr as *const S as u32); + + slice_in_ram_or(buffer_ptr, Error::BufferNotInRAM)?; + + compiler_fence(Ordering::SeqCst); + + let device = Device::::new(); + + device.update_tx(buffer_ptr)?; + + Self::wait_tx_ptr_update().await; + + compiler_fence(Ordering::SeqCst); + + Ok(()) + } + + async fn wait_tx_ptr_update() { + let drop = OnDrop::new(move || { + trace!("TX DROP: Stopping"); + + let device = Device::::new(); + device.disable_tx_ptr_interrupt(); + device.reset_tx_ptr_event(); + device.disable_tx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_tx_ptr_updated() {} + + trace!("TX DROP: Stopped"); + }); + + poll_fn(|cx| { + T::state().tx_waker.register(cx.waker()); + + let device = Device::::new(); + if device.is_tx_ptr_updated() { + trace!("TX POLL: Ready"); + device.reset_tx_ptr_event(); + device.enable_tx_ptr_interrupt(); + Poll::Ready(()) + } else { + trace!("TX POLL: Pending"); + Poll::Pending + } + }) + .await; + + drop.defuse(); + } + + async fn receive_from_ram(buffer_ptr: *mut [S]) -> Result<(), Error> + where + S: Sample, + { + trace!("RECEIVE: {}", buffer_ptr as *const S as u32); + + // NOTE: RAM slice check for rx is not necessary, as a mutable + // slice can only be built from data located in RAM. + + compiler_fence(Ordering::SeqCst); + + let device = Device::::new(); + + device.update_rx(buffer_ptr)?; + + Self::wait_rx_ptr_update().await; + + compiler_fence(Ordering::SeqCst); + + Ok(()) + } + + async fn wait_rx_ptr_update() { + let drop = OnDrop::new(move || { + trace!("RX DROP: Stopping"); + + let device = Device::::new(); + device.disable_rx_ptr_interrupt(); + device.reset_rx_ptr_event(); + device.disable_rx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_rx_ptr_updated() {} + + trace!("RX DROP: Stopped"); + }); + + poll_fn(|cx| { + T::state().rx_waker.register(cx.waker()); + + let device = Device::::new(); + if device.is_rx_ptr_updated() { + trace!("RX POLL: Ready"); + device.reset_rx_ptr_event(); + device.enable_rx_ptr_interrupt(); + Poll::Ready(()) + } else { + trace!("RX POLL: Pending"); + Poll::Pending + } + }) + .await; + + drop.defuse(); + } +} + +/// I2S output +pub struct OutputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, +} + +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> OutputStream<'d, T, S, NB, NS> { + /// Get a mutable reference to the current buffer. + pub fn buffer(&mut self) -> &mut [S] { + self.buffers.get_mut() + } + + /// Prepare the initial buffer and start the I2S transfer. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_tx(); + + device.update_tx(self.buffers.switch())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_tx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sends the current buffer for transmission in the DMA. + /// Switches to use the next available buffer. + pub async fn send(&mut self) -> Result<(), Error> + where + S: Sample, + { + I2S::::send_from_ram(self.buffers.switch()).await + } +} + +/// I2S input +pub struct InputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, +} + +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> InputStream<'d, T, S, NB, NS> { + /// Get a mutable reference to the current buffer. + pub fn buffer(&mut self) -> &mut [S] { + self.buffers.get_mut() + } + + /// Prepare the initial buffer and start the I2S transfer. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_rx(); + + device.update_rx(self.buffers.switch())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_rx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the current buffer for reception from the DMA. + /// Switches to use the next available buffer. + #[allow(unused_mut)] + pub async fn receive(&mut self) -> Result<(), Error> + where + S: Sample, + { + I2S::::receive_from_ram(self.buffers.switch_mut()).await + } +} + +/// I2S full duplex stream (input & output) +pub struct FullDuplexStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers_out: MultiBuffering, + buffers_in: MultiBuffering, +} + +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> FullDuplexStream<'d, T, S, NB, NS> { + /// Get the current output and input buffers. + pub fn buffers(&mut self) -> (&mut [S], &[S]) { + (self.buffers_out.get_mut(), self.buffers_in.get()) + } + + /// Prepare the initial buffers and start the I2S transfer. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_tx(); + device.enable_rx(); + + device.update_tx(self.buffers_out.switch())?; + device.update_rx(self.buffers_in.switch_mut())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_tx_ptr_update().await; + I2S::::wait_rx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the current buffers for output and input for transmission/reception from the DMA. + /// Switch to use the next available buffers for output/input. + pub async fn send_and_receive(&mut self) -> Result<(), Error> + where + S: Sample, + { + I2S::::send_from_ram(self.buffers_out.switch()).await?; + I2S::::receive_from_ram(self.buffers_in.switch_mut()).await?; + Ok(()) + } +} + +/// Helper encapsulating common I2S device operations. +struct Device(&'static RegisterBlock, PhantomData); + +impl Device { + fn new() -> Self { + Self(T::regs(), PhantomData) + } + + #[inline(always)] + pub fn enable(&self) { + trace!("ENABLED"); + self.0.enable.write(|w| w.enable().enabled()); + } + + #[inline(always)] + pub fn disable(&self) { + trace!("DISABLED"); + self.0.enable.write(|w| w.enable().disabled()); + } + + #[inline(always)] + fn enable_tx(&self) { + trace!("TX ENABLED"); + self.0.config.txen.write(|w| w.txen().enabled()); + } + + #[inline(always)] + fn disable_tx(&self) { + trace!("TX DISABLED"); + self.0.config.txen.write(|w| w.txen().disabled()); + } + + #[inline(always)] + fn enable_rx(&self) { + trace!("RX ENABLED"); + self.0.config.rxen.write(|w| w.rxen().enabled()); + } + + #[inline(always)] + fn disable_rx(&self) { + trace!("RX DISABLED"); + self.0.config.rxen.write(|w| w.rxen().disabled()); + } + + #[inline(always)] + fn start(&self) { + trace!("START"); + self.0.tasks_start.write(|w| unsafe { w.bits(1) }); + } + + #[inline(always)] + fn stop(&self) { + self.0.tasks_stop.write(|w| unsafe { w.bits(1) }); + } + + #[inline(always)] + fn is_stopped(&self) -> bool { + self.0.events_stopped.read().bits() != 0 + } + + #[inline(always)] + fn reset_stopped_event(&self) { + trace!("STOPPED EVENT: Reset"); + self.0.events_stopped.reset(); + } + + #[inline(always)] + fn disable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.stopped().clear()); + } + + #[inline(always)] + fn enable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.stopped().set()); + } + + #[inline(always)] + fn reset_tx_ptr_event(&self) { + trace!("TX PTR EVENT: Reset"); + self.0.events_txptrupd.reset(); + } + + #[inline(always)] + fn reset_rx_ptr_event(&self) { + trace!("RX PTR EVENT: Reset"); + self.0.events_rxptrupd.reset(); + } + + #[inline(always)] + fn disable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.txptrupd().clear()); + } + + #[inline(always)] + fn disable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.rxptrupd().clear()); + } + + #[inline(always)] + fn enable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.txptrupd().set()); + } + + #[inline(always)] + fn enable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.rxptrupd().set()); + } + + #[inline(always)] + fn is_tx_ptr_updated(&self) -> bool { + self.0.events_txptrupd.read().bits() != 0 + } + + #[inline(always)] + fn is_rx_ptr_updated(&self) -> bool { + self.0.events_rxptrupd.read().bits() != 0 + } + + #[inline] + fn update_tx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + Ok(()) + } + + #[inline] + fn update_rx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + Ok(()) + } + + fn validated_dma_parts(buffer_ptr: *const [S]) -> Result<(u32, u32), Error> { + let (ptr, len) = slice_ptr_parts(buffer_ptr); + let ptr = ptr as u32; + let bytes_len = len * size_of::(); + let maxcnt = (bytes_len / size_of::()) as u32; + + trace!("PTR={}, MAXCNT={}", ptr, maxcnt); + + if ptr % 4 != 0 { + Err(Error::BufferMisaligned) + } else if bytes_len % 4 != 0 { + Err(Error::BufferLengthMisaligned) + } else if maxcnt as usize > EASY_DMA_SIZE { + Err(Error::BufferTooLong) + } else { + Ok((ptr, maxcnt)) + } + } +} + +/// Sample details +pub trait Sample: Sized + Copy + Default { + /// Width of this sample type. + const WIDTH: usize; + + /// Scale of this sample. + const SCALE: Self; +} + +impl Sample for i8 { + const WIDTH: usize = 8; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +impl Sample for i16 { + const WIDTH: usize = 16; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +impl Sample for i32 { + const WIDTH: usize = 24; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +/// A 4-bytes aligned buffer. Needed for DMA access. +#[derive(Clone, Copy)] +#[repr(align(4))] +pub struct AlignedBuffer([T; N]); + +impl AlignedBuffer { + /// Create a new `AlignedBuffer`. + pub fn new(array: [T; N]) -> Self { + Self(array) + } +} + +impl Default for AlignedBuffer { + fn default() -> Self { + Self([T::default(); N]) + } +} + +impl Deref for AlignedBuffer { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +impl DerefMut for AlignedBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut_slice() + } +} + +/// Set of multiple buffers, for multi-buffering transfers. +pub struct MultiBuffering { + buffers: [AlignedBuffer; NB], + index: usize, +} + +impl MultiBuffering { + /// Create a new `MultiBuffering`. + pub fn new() -> Self { + assert!(NB > 1); + Self { + buffers: [AlignedBuffer::::default(); NB], + index: 0, + } + } + + fn get(&self) -> &[S] { + &self.buffers[self.index] + } + + fn get_mut(&mut self) -> &mut [S] { + &mut self.buffers[self.index] + } + + /// Advance to use the next buffer and return a non mutable pointer to the previous one. + fn switch(&mut self) -> *const [S] { + let prev_index = self.index; + self.index = (self.index + 1) % NB; + self.buffers[prev_index].deref() as *const [S] + } + + /// Advance to use the next buffer and return a mutable pointer to the previous one. + fn switch_mut(&mut self) -> *mut [S] { + let prev_index = self.index; + self.index = (self.index + 1) % NB; + self.buffers[prev_index].deref_mut() as *mut [S] + } +} + +pub(crate) mod sealed { + use core::sync::atomic::AtomicBool; + + use embassy_sync::waitqueue::AtomicWaker; + + /// Peripheral static state + pub struct State { + pub started: AtomicBool, + pub rx_waker: AtomicWaker, + pub tx_waker: AtomicWaker, + pub stop_waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + started: AtomicBool::new(false), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + stop_waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static crate::pac::i2s::RegisterBlock; + fn state() -> &'static State; + } +} + +/// I2S peripehral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +macro_rules! impl_i2s { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::i2s::sealed::Instance for peripherals::$type { + fn regs() -> &'static crate::pac::i2s::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::i2s::sealed::State { + static STATE: crate::i2s::sealed::State = crate::i2s::sealed::State::new(); + &STATE + } + } + impl crate::i2s::Instance for peripherals::$type { + type Interrupt = crate::interrupt::$irq; + } + }; +} diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index d7bd21702..20e70a248 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -1,49 +1,11 @@ -//! # Embassy nRF HAL -//! -//! HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. -//! -//! The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs -//! for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to -//! complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. -//! -//! ## EasyDMA considerations -//! -//! On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting -//! with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI). -//! However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust -//! slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen: -//! -//! ```no_run -//! // As we pass a slice to the function whose contents will not ever change, -//! // the compiler writes it into the flash and thus the pointer to it will -//! // reference static memory. Since EasyDMA requires slices to reside in RAM, -//! // this function call will fail. -//! let result = spim.write_from_ram(&[1, 2, 3]); -//! assert_eq!(result, Err(Error::DMABufferNotInDataMemory)); -//! -//! // The data is still static and located in flash. However, since we are assigning -//! // it to a variable, the compiler will load it into memory. Passing a reference to the -//! // variable will yield a pointer that references dynamic memory, thus making EasyDMA happy. -//! // This function call succeeds. -//! let data = [1, 2, 3]; -//! let result = spim.write_from_ram(&data); -//! assert!(result.is_ok()); -//! ``` -//! -//! Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions: -//! - Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM. -//! - Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission. -//! -//! Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will -//! fail and notify you, or the more convenient versions without the suffix which are potentially a little bit -//! more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage -//! as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840). -//! -//! Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as -//! mutable slices always reside in RAM. - #![no_std] -#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] +#![cfg_attr( + feature = "nightly", + feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections) +)] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] #[cfg(not(any( feature = "nrf51", @@ -74,8 +36,17 @@ pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] +#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] +pub mod i2s; pub mod nvmc; +#[cfg(any( + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf9160" +))] +pub mod pdm; pub mod ppi; #[cfg(not(any(feature = "nrf52805", feature = "nrf52820", feature = "_nrf5340-net")))] pub mod pwm; @@ -88,10 +59,12 @@ pub mod rng; #[cfg(not(any(feature = "nrf52820", feature = "_nrf5340-net")))] pub mod saadc; pub mod spim; +pub mod spis; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod temp; pub mod timer; pub mod twim; +pub mod twis; pub mod uarte; #[cfg(any( feature = "_nrf5340-app", @@ -259,5 +232,12 @@ pub fn init(config: config::Config) -> Peripherals { #[cfg(feature = "_time-driver")] time_driver::init(config.time_interrupt_priority); + // Disable UARTE (enabled by default for some reason) + #[cfg(feature = "_nrf9160")] + unsafe { + (*pac::UARTE0::ptr()).enable.write(|w| w.enable().disabled()); + (*pac::UARTE1::ptr()).enable.write(|w| w.enable().disabled()); + } + peripherals } diff --git a/embassy-nrf/src/nvmc.rs b/embassy-nrf/src/nvmc.rs index 6f66f7a78..c1ffa31aa 100644 --- a/embassy-nrf/src/nvmc.rs +++ b/embassy-nrf/src/nvmc.rs @@ -1,4 +1,4 @@ -//! Non-Volatile Memory Controller (NVMC) module. +//! Non-Volatile Memory Controller (NVMC, AKA internal flash) driver. use core::{ptr, slice}; @@ -10,8 +10,12 @@ use embedded_storage::nor_flash::{ use crate::peripherals::NVMC; use crate::{pac, Peripheral}; +#[cfg(not(feature = "_nrf5340-net"))] /// Erase size of NVMC flash in bytes. pub const PAGE_SIZE: usize = 4096; +#[cfg(feature = "_nrf5340-net")] +/// Erase size of NVMC flash in bytes. +pub const PAGE_SIZE: usize = 2048; /// Size of NVMC flash in bytes. pub const FLASH_SIZE: usize = crate::chip::FLASH_SIZE; @@ -55,6 +59,51 @@ impl<'d> Nvmc<'d> { let p = Self::regs(); while p.ready.read().ready().is_busy() {} } + + #[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))] + fn wait_ready_write(&mut self) { + self.wait_ready(); + } + + #[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))] + fn wait_ready_write(&mut self) { + let p = Self::regs(); + while p.readynext.read().readynext().is_busy() {} + } + + #[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))] + fn erase_page(&mut self, page_addr: u32) { + Self::regs().erasepage().write(|w| unsafe { w.bits(page_addr) }); + } + + #[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))] + fn erase_page(&mut self, page_addr: u32) { + let first_page_word = page_addr as *mut u32; + unsafe { + first_page_word.write_volatile(0xFFFF_FFFF); + } + } + + fn enable_erase(&self) { + #[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] + Self::regs().config.write(|w| w.wen().een()); + #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] + Self::regs().configns.write(|w| w.wen().een()); + } + + fn enable_read(&self) { + #[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] + Self::regs().config.write(|w| w.wen().ren()); + #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] + Self::regs().configns.write(|w| w.wen().ren()); + } + + fn enable_write(&self) { + #[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] + Self::regs().config.write(|w| w.wen().wen()); + #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] + Self::regs().configns.write(|w| w.wen().wen()); + } } impl<'d> MultiwriteNorFlash for Nvmc<'d> {} @@ -93,17 +142,15 @@ impl<'d> NorFlash for Nvmc<'d> { return Err(Error::Unaligned); } - let p = Self::regs(); - - p.config.write(|w| w.wen().een()); + self.enable_erase(); self.wait_ready(); - for page in (from..to).step_by(PAGE_SIZE) { - p.erasepage().write(|w| unsafe { w.bits(page) }); + for page_addr in (from..to).step_by(PAGE_SIZE) { + self.erase_page(page_addr); self.wait_ready(); } - p.config.reset(); + self.enable_read(); self.wait_ready(); Ok(()) @@ -117,9 +164,7 @@ impl<'d> NorFlash for Nvmc<'d> { return Err(Error::Unaligned); } - let p = Self::regs(); - - p.config.write(|w| w.wen().wen()); + self.enable_write(); self.wait_ready(); unsafe { @@ -129,11 +174,11 @@ impl<'d> NorFlash for Nvmc<'d> { for i in 0..words { let w = ptr::read_unaligned(p_src.add(i)); ptr::write_volatile(p_dst.add(i), w); - self.wait_ready(); + self.wait_ready_write(); } } - p.config.reset(); + self.enable_read(); self.wait_ready(); Ok(()) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs new file mode 100644 index 000000000..54feca4c1 --- /dev/null +++ b/embassy-nrf/src/pdm.rs @@ -0,0 +1,254 @@ +//! Pulse Density Modulation (PDM) mirophone driver. + +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::drop::OnDrop; +use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use futures::future::poll_fn; + +use crate::chip::EASY_DMA_SIZE; +use crate::gpio::sealed::Pin; +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::{self, InterruptExt}; +use crate::peripherals::PDM; +use crate::{pac, Peripheral}; + +/// PDM microphone interface +pub struct Pdm<'d> { + irq: PeripheralRef<'d, interrupt::PDM>, + phantom: PhantomData<&'d PDM>, +} + +/// PDM error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Buffer is too long. + BufferTooLong, + /// Buffer is empty + BufferZeroLength, + /// PDM is not running + NotRunning, +} + +static WAKER: AtomicWaker = AtomicWaker::new(); +static DUMMY_BUFFER: [i16; 1] = [0; 1]; + +impl<'d> Pdm<'d> { + /// Create PDM driver + pub fn new( + pdm: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(clk, din); + Self::new_inner(pdm, irq, clk.map_into(), din.map_into(), config) + } + + fn new_inner( + _pdm: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + clk: PeripheralRef<'d, AnyPin>, + din: PeripheralRef<'d, AnyPin>, + config: Config, + ) -> Self { + into_ref!(irq); + + let r = Self::regs(); + + // setup gpio pins + din.conf().write(|w| w.input().set_bit()); + r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) }); + clk.set_low(); + clk.conf().write(|w| w.dir().output()); + r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) }); + + // configure + // use default for + // - gain right + // - gain left + // - clk + // - ratio + r.mode.write(|w| { + w.edge().bit(config.edge == Edge::LeftRising); + w.operation().bit(config.operation_mode == OperationMode::Mono); + w + }); + r.gainl.write(|w| w.gainl().default_gain()); + r.gainr.write(|w| w.gainr().default_gain()); + + // IRQ + irq.disable(); + irq.set_handler(|_| { + let r = Self::regs(); + r.intenclr.write(|w| w.end().clear()); + WAKER.wake(); + }); + irq.enable(); + + r.enable.write(|w| w.enable().set_bit()); + + Self { + phantom: PhantomData, + irq, + } + } + + /// Start sampling microphon data into a dummy buffer + /// Usefull to start the microphon and keep it active between recording samples + pub async fn start(&mut self) { + let r = Self::regs(); + + // start dummy sampling because microphon needs some setup time + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); + r.sample + .maxcnt + .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + + r.tasks_start.write(|w| w.tasks_start().set_bit()); + } + + /// Stop sampling microphon data inta a dummy buffer + pub async fn stop(&mut self) { + let r = Self::regs(); + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + r.events_started.reset(); + } + + /// Sample data into the given buffer. + pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> { + if buffer.len() == 0 { + return Err(Error::BufferZeroLength); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let r = Self::regs(); + + if r.events_started.read().events_started().bit_is_clear() { + return Err(Error::NotRunning); + } + + let drop = OnDrop::new(move || { + r.intenclr.write(|w| w.end().clear()); + r.events_stopped.reset(); + + // reset to dummy buffer + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); + r.sample + .maxcnt + .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + + while r.events_stopped.read().bits() == 0 {} + }); + + // setup user buffer + let ptr = buffer.as_ptr(); + let len = buffer.len(); + r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) }); + r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) }); + + // wait till the current sample is finished and the user buffer sample is started + Self::wait_for_sample().await; + + // reset the buffer back to the dummy buffer + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); + r.sample + .maxcnt + .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + + // wait till the user buffer is sampled + Self::wait_for_sample().await; + + drop.defuse(); + + Ok(()) + } + + async fn wait_for_sample() { + let r = Self::regs(); + + r.events_end.reset(); + r.intenset.write(|w| w.end().set()); + + compiler_fence(Ordering::SeqCst); + + poll_fn(|cx| { + WAKER.register(cx.waker()); + if r.events_end.read().events_end().bit_is_set() { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + } + + fn regs() -> &'static pac::pdm::RegisterBlock { + unsafe { &*pac::PDM::ptr() } + } +} + +/// PDM microphone driver Config +pub struct Config { + /// Use stero or mono operation + pub operation_mode: OperationMode, + /// On which edge the left channel should be samples + pub edge: Edge, +} + +impl Default for Config { + fn default() -> Self { + Self { + operation_mode: OperationMode::Mono, + edge: Edge::LeftFalling, + } + } +} + +/// PDM operation mode. +#[derive(PartialEq)] +pub enum OperationMode { + /// Mono (1 channel) + Mono, + /// Stereo (2 channels) + Stereo, +} + +/// PDM edge polarity +#[derive(PartialEq)] +pub enum Edge { + /// Left edge is rising + LeftRising, + /// Left edge is falling + LeftFalling, +} + +impl<'d> Drop for Pdm<'d> { + fn drop(&mut self) { + let r = Self::regs(); + + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + + self.irq.disable(); + + r.enable.write(|w| w.enable().disabled()); + + r.psel.din.reset(); + r.psel.clk.reset(); + } +} diff --git a/embassy-nrf/src/ppi/dppi.rs b/embassy-nrf/src/ppi/dppi.rs index de856c0ca..0908cd7be 100644 --- a/embassy-nrf/src/ppi/dppi.rs +++ b/embassy-nrf/src/ppi/dppi.rs @@ -11,12 +11,14 @@ fn regs() -> &'static pac::dppic::RegisterBlock { } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { + /// Configure PPI channel to trigger `task` on `event`. pub fn new_one_to_one(ch: impl Peripheral

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

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

+ 'd, events: [Event; EVENT_COUNT], diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs index 8f5ed14cd..b76eccf0b 100644 --- a/embassy-nrf/src/ppi/mod.rs +++ b/embassy-nrf/src/ppi/mod.rs @@ -1,6 +1,6 @@ #![macro_use] -//! HAL interface for the PPI and DPPI peripheral. +//! Programmable Peripheral Interconnect (PPI/DPPI) driver. //! //! The (Distributed) Programmable Peripheral Interconnect interface allows for an autonomous interoperability //! between peripherals through their events and tasks. There are fixed PPI channels and fully diff --git a/embassy-nrf/src/ppi/ppi.rs b/embassy-nrf/src/ppi/ppi.rs index 19abc4e18..a96ab50b7 100644 --- a/embassy-nrf/src/ppi/ppi.rs +++ b/embassy-nrf/src/ppi/ppi.rs @@ -48,7 +48,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { #[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { - /// Configure PPI channel to trigger `task1` and `task2` on `event`. + /// Configure PPI channel to trigger both `task1` and `task2` on `event`. pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event, task1: Task, task2: Task) -> Self { into_ref!(ch); diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 5f750a91e..708f23104 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -1,3 +1,5 @@ +//! Pulse Width Modulation (PWM) driver. + #![macro_use] use core::sync::atomic::{compiler_fence, Ordering}; @@ -32,6 +34,7 @@ pub struct SequencePwm<'d, T: Instance> { ch3: Option>, } +/// PWM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -41,7 +44,7 @@ pub enum Error { /// Min Sequence count is 1 SequenceTimesAtLeastOne, /// EasyDMA can only read from data memory, read only buffers in flash will fail. - DMABufferNotInDataMemory, + BufferNotInRAM, } const MAX_SEQUENCE_LEN: usize = 32767; @@ -358,6 +361,7 @@ pub struct Sequence<'s> { } impl<'s> Sequence<'s> { + /// Create a new `Sequence` pub fn new(words: &'s [u16], config: SequenceConfig) -> Self { Self { words, config } } @@ -367,7 +371,7 @@ impl<'s> Sequence<'s> { /// Takes at one sequence along with its configuration. #[non_exhaustive] pub struct SingleSequencer<'d, 's, T: Instance> { - pub sequencer: Sequencer<'d, 's, T>, + sequencer: Sequencer<'d, 's, T>, } impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { @@ -428,8 +432,8 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { let sequence0 = &self.sequence0; let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0); - slice_in_ram_or(sequence0.words, Error::DMABufferNotInDataMemory)?; - slice_in_ram_or(alt_sequence.words, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(sequence0.words, Error::BufferNotInRAM)?; + slice_in_ram_or(alt_sequence.words, Error::BufferNotInRAM)?; if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN { return Err(Error::SequenceTooLong); @@ -536,13 +540,21 @@ pub enum SequenceMode { /// PWM Base clock is system clock (16MHz) divided by prescaler #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Prescaler { + /// Divide by 1 Div1, + /// Divide by 2 Div2, + /// Divide by 4 Div4, + /// Divide by 8 Div8, + /// Divide by 16 Div16, + /// Divide by 32 Div32, + /// Divide by 64 Div64, + /// Divide by 128 Div128, } @@ -828,7 +840,9 @@ pub(crate) mod sealed { } } +/// PWM peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/qdec.rs b/embassy-nrf/src/qdec.rs index 253c85c32..c01babca3 100644 --- a/embassy-nrf/src/qdec.rs +++ b/embassy-nrf/src/qdec.rs @@ -1,4 +1,4 @@ -//! Quadrature decoder interface +//! Quadrature decoder (QDEC) driver. use core::future::poll_fn; use core::task::Poll; @@ -12,17 +12,23 @@ use crate::interrupt::InterruptExt; use crate::peripherals::QDEC; use crate::{interrupt, pac, Peripheral}; -/// Quadrature decoder +/// Quadrature decoder driver. pub struct Qdec<'d> { _p: PeripheralRef<'d, QDEC>, } +/// QDEC config #[non_exhaustive] pub struct Config { + /// Number of samples pub num_samples: NumSamples, + /// Sample period pub period: SamplePeriod, + /// Set LED output pin polarity pub led_polarity: LedPolarity, + /// Enable/disable input debounce filters pub debounce: bool, + /// Time period the LED is switched ON prior to sampling (0..511 us). pub led_pre_usecs: u16, } @@ -41,6 +47,7 @@ impl Default for Config { static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d> Qdec<'d> { + /// Create a new QDEC. pub fn new( qdec: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -52,6 +59,7 @@ impl<'d> Qdec<'d> { Self::new_inner(qdec, irq, a.map_into(), b.map_into(), None, config) } + /// Create a new QDEC, with a pin for LED output. pub fn new_with_led( qdec: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -170,36 +178,61 @@ impl<'d> Qdec<'d> { } } +/// Sample period #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SamplePeriod { + /// 128 us _128us, + /// 256 us _256us, + /// 512 us _512us, + /// 1024 us _1024us, + /// 2048 us _2048us, + /// 4096 us _4096us, + /// 8192 us _8192us, + /// 16384 us _16384us, + /// 32 ms _32ms, + /// 65 ms _65ms, + /// 131 ms _131ms, } +/// Number of samples taken. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum NumSamples { + /// 10 samples _10smpl, + /// 40 samples _40smpl, + /// 80 samples _80smpl, + /// 120 samples _120smpl, + /// 160 samples _160smpl, + /// 200 samples _200smpl, + /// 240 samples _240smpl, + /// 280 samples _280smpl, + /// 1 sample _1smpl, } +/// LED polarity #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum LedPolarity { + /// Active high (a high output turns on the LED). ActiveHigh, + /// Active low (a low output turns on the LED). ActiveLow, } diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs index ea0a17031..d434327fc 100644 --- a/embassy-nrf/src/qspi.rs +++ b/embassy-nrf/src/qspi.rs @@ -1,10 +1,12 @@ +//! Quad Serial Peripheral Interface (QSPI) flash driver. + #![macro_use] use core::future::poll_fn; use core::ptr; use core::task::Poll; -use embassy_hal_common::drop::DropBomb; +use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use crate::gpio::{self, Pin as GpioPin}; @@ -15,6 +17,7 @@ pub use crate::pac::qspi::ifconfig0::{ pub use crate::pac::qspi::ifconfig1::SPIMODE_A as SpiMode; use crate::{pac, Peripheral}; +/// Deep power-down config. pub struct DeepPowerDownConfig { /// Time required for entering DPM, in units of 16us pub enter_time: u16, @@ -22,37 +25,62 @@ pub struct DeepPowerDownConfig { pub exit_time: u16, } +/// QSPI bus frequency. pub enum Frequency { + /// 32 Mhz M32 = 0, + /// 16 Mhz M16 = 1, + /// 10.7 Mhz M10_7 = 2, + /// 8 Mhz M8 = 3, + /// 6.4 Mhz M6_4 = 4, + /// 5.3 Mhz M5_3 = 5, + /// 4.6 Mhz M4_6 = 6, + /// 4 Mhz M4 = 7, + /// 3.6 Mhz M3_6 = 8, + /// 3.2 Mhz M3_2 = 9, + /// 2.9 Mhz M2_9 = 10, + /// 2.7 Mhz M2_7 = 11, + /// 2.5 Mhz M2_5 = 12, + /// 2.3 Mhz M2_3 = 13, + /// 2.1 Mhz M2_1 = 14, + /// 2 Mhz M2 = 15, } +/// QSPI config. #[non_exhaustive] pub struct Config { + /// XIP offset. pub xip_offset: u32, + /// Opcode used for read operations. pub read_opcode: ReadOpcode, + /// Opcode used for write operations. pub write_opcode: WriteOpcode, + /// Page size for write operations. pub write_page_size: WritePageSize, + /// Configuration for deep power down. If None, deep power down is disabled. pub deep_power_down: Option, + /// QSPI bus frequency. pub frequency: Frequency, /// Value is specified in number of 16 MHz periods (62.5 ns) pub sck_delay: u8, /// Whether data is captured on the clock rising edge and data is output on a falling edge (MODE0) or vice-versa (MODE3) pub spi_mode: SpiMode, + /// Addressing mode (24-bit or 32-bit) pub address_mode: AddressMode, } @@ -72,20 +100,24 @@ impl Default for Config { } } +/// Error #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// Operation address was out of bounds. OutOfBounds, // TODO add "not in data memory" error and check for it } +/// QSPI flash driver. pub struct Qspi<'d, T: Instance, const FLASH_SIZE: usize> { irq: PeripheralRef<'d, T::Interrupt>, dpm_enabled: bool, } impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { + /// Create a new QSPI driver. pub fn new( _qspi: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -158,7 +190,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { // Enable it r.enable.write(|w| w.enable().enabled()); - let mut res = Self { + let res = Self { dpm_enabled: config.deep_power_down.is_some(), irq, }; @@ -168,7 +200,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { r.tasks_activate.write(|w| w.tasks_activate().bit(true)); - res.blocking_wait_ready(); + Self::blocking_wait_ready(); res } @@ -183,8 +215,9 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { } } + /// Do a custom QSPI instruction. pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); let len = core::cmp::max(req.len(), resp.len()) as u8; self.custom_instruction_start(opcode, req, len)?; @@ -193,16 +226,17 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { self.custom_instruction_finish(resp)?; - bomb.defuse(); + ondrop.defuse(); Ok(()) } + /// Do a custom QSPI instruction, blocking version. pub fn blocking_custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { let len = core::cmp::max(req.len(), resp.len()) as u8; self.custom_instruction_start(opcode, req, len)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); self.custom_instruction_finish(resp)?; @@ -278,7 +312,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { .await } - fn blocking_wait_ready(&mut self) { + fn blocking_wait_ready() { loop { let r = T::regs(); if r.events_ready.read().bits() != 0 { @@ -346,54 +380,60 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } + /// Read data from the flash memory. pub async fn read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_read(address, data)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } + /// Write data to the flash memory. pub async fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_write(address, data)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } + /// Erase a sector on the flash memory. pub async fn erase(&mut self, address: usize) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_erase(address)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } + /// Read data from the flash memory, blocking version. pub fn blocking_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { self.start_read(address, data)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } + /// Write data to the flash memory, blocking version. pub fn blocking_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { self.start_write(address, data)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } + /// Erase a sector on the flash memory, blocking version. pub fn blocking_erase(&mut self, address: usize) -> Result<(), Error> { self.start_erase(address)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } } @@ -547,7 +587,9 @@ pub(crate) mod sealed { } } +/// QSPI peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/rng.rs b/embassy-nrf/src/rng.rs index e0caeaaee..b0b3a8eb8 100644 --- a/embassy-nrf/src/rng.rs +++ b/embassy-nrf/src/rng.rs @@ -1,3 +1,5 @@ +//! Random Number Generator (RNG) driver. + use core::future::poll_fn; use core::ptr; use core::sync::atomic::{AtomicPtr, Ordering}; @@ -128,10 +130,11 @@ impl<'d> Rng<'d> { /// However, this makes the generation of numbers slower. /// /// Defaults to disabled. - pub fn bias_correction(&self, enable: bool) { + pub fn set_bias_correction(&self, enable: bool) { RNG::regs().config.write(|w| w.dercen().bit(enable)) } + /// Fill the buffer with random bytes. pub async fn fill_bytes(&mut self, dest: &mut [u8]) { if dest.len() == 0 { return; // Nothing to fill @@ -175,6 +178,7 @@ impl<'d> Rng<'d> { drop(on_drop); } + /// Fill the buffer with random bytes, blocking version. pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { self.start(); diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index d1c82423e..2d01a3dda 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -1,3 +1,5 @@ +//! Successive Approximation Analog-to-Digital Converter (SAADC) driver. + #![macro_use] use core::future::poll_fn; @@ -20,6 +22,7 @@ use crate::ppi::{ConfigurableChannel, Event, Ppi, Task}; use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::{interrupt, pac, peripherals, Peripheral}; +/// SAADC error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -102,17 +105,17 @@ impl<'d> ChannelConfig<'d> { } } -/// The state of a continuously running sampler. While it reflects -/// the progress of a sampler, it also signals what should be done -/// next. For example, if the sampler has stopped then the Saadc implementation -/// can then tear down its infrastructure. +/// Value returned by the SAADC callback, deciding what happens next. #[derive(PartialEq)] -pub enum SamplerState { - Sampled, - Stopped, +pub enum CallbackResult { + /// The SAADC should keep sampling and calling the callback. + Continue, + /// The SAADC should stop sampling, and return. + Stop, } impl<'d, const N: usize> Saadc<'d, N> { + /// Create a new SAADC driver. pub fn new( saadc: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -225,7 +228,7 @@ impl<'d, const N: usize> Saadc<'d, N> { /// also cause the sampling to be stopped. pub async fn sample(&mut self, buf: &mut [i16; N]) { // In case the future is dropped, stop the task and wait for it to end. - OnDrop::new(Self::stop_sampling_immediately); + let on_drop = OnDrop::new(Self::stop_sampling_immediately); let r = Self::regs(); @@ -258,6 +261,8 @@ impl<'d, const N: usize> Saadc<'d, N> { Poll::Pending }) .await; + + drop(on_drop); } /// Continuous sampling with double buffers. @@ -283,7 +288,7 @@ impl<'d, const N: usize> Saadc<'d, N> { /// free the buffers from being used by the peripheral. Cancellation will /// also cause the sampling to be stopped. - pub async fn run_task_sampler( + pub async fn run_task_sampler( &mut self, timer: &mut T, ppi_ch1: &mut impl ConfigurableChannel, @@ -291,9 +296,9 @@ impl<'d, const N: usize> Saadc<'d, N> { frequency: Frequency, sample_counter: u32, bufs: &mut [[[i16; N]; N0]; 2], - sampler: S, + callback: F, ) where - S: FnMut(&[[i16; N]]) -> SamplerState, + F: FnMut(&[[i16; N]]) -> CallbackResult, { let r = Self::regs(); @@ -319,23 +324,23 @@ impl<'d, const N: usize> Saadc<'d, N> { || { sample_ppi.enable(); }, - sampler, + callback, ) .await; } - async fn run_sampler( + async fn run_sampler( &mut self, bufs: &mut [[[i16; N]; N0]; 2], sample_rate_divisor: Option, mut init: I, - mut sampler: S, + mut callback: F, ) where I: FnMut(), - S: FnMut(&[[i16; N]]) -> SamplerState, + F: FnMut(&[[i16; N]]) -> CallbackResult, { // In case the future is dropped, stop the task and wait for it to end. - OnDrop::new(Self::stop_sampling_immediately); + let on_drop = OnDrop::new(Self::stop_sampling_immediately); let r = Self::regs(); @@ -382,7 +387,7 @@ impl<'d, const N: usize> Saadc<'d, N> { let mut current_buffer = 0; // Wait for events and complete when the sampler indicates it has had enough. - poll_fn(|cx| { + let r = poll_fn(|cx| { let r = Self::regs(); WAKER.register(cx.waker()); @@ -393,12 +398,15 @@ impl<'d, const N: usize> Saadc<'d, N> { r.events_end.reset(); r.intenset.write(|w| w.end().set()); - if sampler(&bufs[current_buffer]) == SamplerState::Sampled { - let next_buffer = 1 - current_buffer; - current_buffer = next_buffer; - } else { - return Poll::Ready(()); - }; + match callback(&bufs[current_buffer]) { + CallbackResult::Continue => { + let next_buffer = 1 - current_buffer; + current_buffer = next_buffer; + } + CallbackResult::Stop => { + return Poll::Ready(()); + } + } } if r.events_started.read().bits() != 0 { @@ -419,6 +427,10 @@ impl<'d, const N: usize> Saadc<'d, N> { Poll::Pending }) .await; + + drop(on_drop); + + r } // Stop sampling and wait for it to stop in a blocking fashion @@ -452,7 +464,7 @@ impl<'d> Saadc<'d, 1> { sample_rate_divisor: u16, sampler: S, ) where - S: FnMut(&[[i16; 1]]) -> SamplerState, + S: FnMut(&[[i16; 1]]) -> CallbackResult, { self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler).await; } @@ -652,6 +664,10 @@ pub(crate) mod sealed { /// An input that can be used as either or negative end of a ADC differential in the SAADC periperhal. pub trait Input: sealed::Input + Into + Peripheral

+ Sized + 'static { + /// Convert this SAADC input to a type-erased `AnyInput`. + /// + /// This allows using several inputs in situations that might require + /// them to be the same type, like putting them in an array. fn degrade_saadc(self) -> AnyInput { AnyInput { channel: self.channel(), @@ -659,6 +675,10 @@ pub trait Input: sealed::Input + Into + Peripheral

+ Sized + } } +/// A type-erased SAADC input. +/// +/// This allows using several inputs in situations that might require +/// them to be the same type, like putting them in an array. pub struct AnyInput { channel: InputChannel, } diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index d821d2353..17e435787 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -1,3 +1,5 @@ +//! Serial Peripheral Instance in master mode (SPIM) driver. + #![macro_use] use core::future::poll_fn; @@ -16,27 +18,37 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; use crate::{pac, Peripheral}; +/// SPIM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// RX buffer was too long. RxBufferTooLong, /// EasyDMA can only read from data memory, read only buffers in flash will fail. - DMABufferNotInDataMemory, + BufferNotInRAM, } -/// Interface for the SPIM peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// SPIM driver. pub struct Spim<'d, T: Instance> { _p: PeripheralRef<'d, T>, } +/// SPIM configuration. #[non_exhaustive] pub struct Config { + /// Frequency pub frequency: Frequency, + + /// SPI mode pub mode: Mode, + + /// Overread character. + /// + /// When doing bidirectional transfers, if the TX buffer is shorter than the RX buffer, + /// this byte will be transmitted in the MOSI line for the left-over bytes. pub orc: u8, } @@ -51,6 +63,7 @@ impl Default for Config { } impl<'d, T: Instance> Spim<'d, T> { + /// Create a new SPIM driver. pub fn new( spim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -70,6 +83,7 @@ impl<'d, T: Instance> Spim<'d, T> { ) } + /// Create a new SPIM driver, capable of TX only (MOSI only). pub fn new_txonly( spim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -81,6 +95,7 @@ impl<'d, T: Instance> Spim<'d, T> { Self::new_inner(spim, irq, sck.map_into(), None, Some(mosi.map_into()), config) } + /// Create a new SPIM driver, capable of RX only (MISO only). pub fn new_rxonly( spim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -194,7 +209,7 @@ impl<'d, T: Instance> Spim<'d, T> { } fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { - slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(tx, Error::BufferNotInRAM)?; // NOTE: RAM slice check for rx is not necessary, as a mutable // slice can only be built from data located in RAM. @@ -236,7 +251,7 @@ impl<'d, T: Instance> Spim<'d, T> { fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { match self.blocking_inner_from_ram(rx, tx) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -268,7 +283,7 @@ impl<'d, T: Instance> Spim<'d, T> { async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { match self.async_inner_from_ram(rx, tx).await { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -385,7 +400,9 @@ pub(crate) mod sealed { } } +/// SPIM peripheral instance pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } @@ -437,7 +454,7 @@ mod eh1 { match *self { Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::spi::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other, } } } @@ -477,45 +494,34 @@ mod eh1 { #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eha { - use core::future::Future; use super::*; impl<'d, T: Instance> embedded_hal_async::spi::SpiBusFlush for Spim<'d, T> { - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } + async fn flush(&mut self) -> Result<(), Error> { + Ok(()) } } impl<'d, T: Instance> embedded_hal_async::spi::SpiBusRead for Spim<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, words: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(words) + async fn read(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.read(words).await } } impl<'d, T: Instance> embedded_hal_async::spi::SpiBusWrite for Spim<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(data) + async fn write(&mut self, data: &[u8]) -> Result<(), Error> { + self.write(data).await } } impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spim<'d, T> { - type TransferFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer<'a>(&'a mut self, rx: &'a mut [u8], tx: &'a [u8]) -> Self::TransferFuture<'a> { - self.transfer(rx, tx) + async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { + self.transfer(rx, tx).await } - type TransferInPlaceFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer_in_place<'a>(&'a mut self, words: &'a mut [u8]) -> Self::TransferInPlaceFuture<'a> { - self.transfer_in_place(words) + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_in_place(words).await } } } diff --git a/embassy-nrf/src/spis.rs b/embassy-nrf/src/spis.rs new file mode 100644 index 000000000..1b7436477 --- /dev/null +++ b/embassy-nrf/src/spis.rs @@ -0,0 +1,561 @@ +//! Serial Peripheral Instance in slave mode (SPIS) driver. + +#![macro_use] +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_common::{into_ref, PeripheralRef}; +pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; + +use crate::chip::FORCE_COPY_BUFFER_SIZE; +use crate::gpio::sealed::Pin as _; +use crate::gpio::{self, AnyPin, Pin as GpioPin}; +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; +use crate::{pac, Peripheral}; + +/// SPIS error +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// TX buffer was too long. + TxBufferTooLong, + /// RX buffer was too long. + RxBufferTooLong, + /// EasyDMA can only read from data memory, read only buffers in flash will fail. + BufferNotInRAM, +} + +/// SPIS driver. +pub struct Spis<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +/// SPIS configuration. +#[non_exhaustive] +pub struct Config { + /// SPI mode + pub mode: Mode, + + /// Overread character. + /// + /// If the master keeps clocking the bus after all the bytes in the TX buffer have + /// already been transmitted, this byte will be constantly transmitted in the MISO line. + pub orc: u8, + + /// Default byte. + /// + /// This is the byte clocked out in the MISO line for ignored transactions (if the master + /// sets CSN low while the semaphore is owned by the firmware) + pub def: u8, + + /// Automatically make the firmware side acquire the semaphore on transfer end. + pub auto_acquire: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: MODE_0, + orc: 0x00, + def: 0x00, + auto_acquire: true, + } + } +} + +impl<'d, T: Instance> Spis<'d, T> { + /// Create a new SPIS driver. + pub fn new( + spis: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + cs: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + mosi: impl Peripheral

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

+ 'd, + irq: impl Peripheral

+ 'd, + cs: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + miso: impl Peripheral

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

+ 'd, + irq: impl Peripheral

+ 'd, + cs: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cs, sck, mosi); + Self::new_inner( + spis, + irq, + cs.map_into(), + sck.map_into(), + None, + Some(mosi.map_into()), + config, + ) + } + + fn new_inner( + spis: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + cs: PeripheralRef<'d, AnyPin>, + sck: PeripheralRef<'d, AnyPin>, + miso: Option>, + mosi: Option>, + config: Config, + ) -> Self { + compiler_fence(Ordering::SeqCst); + + into_ref!(spis, irq, cs, sck); + + let r = T::regs(); + + // Configure pins. + sck.conf().write(|w| w.input().connect().drive().h0h1()); + r.psel.sck.write(|w| unsafe { w.bits(sck.psel_bits()) }); + cs.conf().write(|w| w.input().connect().drive().h0h1()); + r.psel.csn.write(|w| unsafe { w.bits(cs.psel_bits()) }); + if let Some(mosi) = &mosi { + mosi.conf().write(|w| w.input().connect().drive().h0h1()); + r.psel.mosi.write(|w| unsafe { w.bits(mosi.psel_bits()) }); + } + if let Some(miso) = &miso { + miso.conf().write(|w| w.dir().output().drive().h0h1()); + r.psel.miso.write(|w| unsafe { w.bits(miso.psel_bits()) }); + } + + // Enable SPIS instance. + r.enable.write(|w| w.enable().enabled()); + + // Configure mode. + let mode = config.mode; + r.config.write(|w| { + match mode { + MODE_0 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().leading(); + } + MODE_1 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().trailing(); + } + MODE_2 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().leading(); + } + MODE_3 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().trailing(); + } + } + + w + }); + + // Set over-read character. + let orc = config.orc; + r.orc.write(|w| unsafe { w.orc().bits(orc) }); + + // Set default character. + let def = config.def; + r.def.write(|w| unsafe { w.def().bits(def) }); + + // Configure auto-acquire on 'transfer end' event. + if config.auto_acquire { + r.shorts.write(|w| w.end_acquire().bit(true)); + } + + // Disable all events interrupts. + r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + irq.set_handler(Self::on_interrupt); + irq.unpend(); + irq.enable(); + + Self { _p: spis } + } + + fn on_interrupt(_: *mut ()) { + let r = T::regs(); + let s = T::state(); + + if r.events_end.read().bits() != 0 { + s.waker.wake(); + r.intenclr.write(|w| w.end().clear()); + } + + if r.events_acquired.read().bits() != 0 { + s.waker.wake(); + r.intenclr.write(|w| w.acquired().clear()); + } + } + + fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { + slice_in_ram_or(tx, Error::BufferNotInRAM)?; + // NOTE: RAM slice check for rx is not necessary, as a mutable + // slice can only be built from data located in RAM. + + compiler_fence(Ordering::SeqCst); + + let r = T::regs(); + + // Set up the DMA write. + let (ptr, len) = slice_ptr_parts(tx); + r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) }); + r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + // Set up the DMA read. + let (ptr, len) = slice_ptr_parts_mut(rx); + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + // Reset end event. + r.events_end.reset(); + + // Release the semaphore. + r.tasks_release.write(|w| unsafe { w.bits(1) }); + + Ok(()) + } + + fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(usize, usize), Error> { + compiler_fence(Ordering::SeqCst); + let r = T::regs(); + + // Acquire semaphore. + if r.semstat.read().bits() != 1 { + r.events_acquired.reset(); + r.tasks_acquire.write(|w| unsafe { w.bits(1) }); + // Wait until CPU has acquired the semaphore. + while r.semstat.read().bits() != 1 {} + } + + self.prepare(rx, tx)?; + + // Wait for 'end' event. + while r.events_end.read().bits() == 0 {} + + let n_rx = r.rxd.amount.read().bits() as usize; + let n_tx = r.txd.amount.read().bits() as usize; + + compiler_fence(Ordering::SeqCst); + + Ok((n_rx, n_tx)) + } + + fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { + match self.blocking_inner_from_ram(rx, tx) { + Ok(n) => Ok(n), + Err(Error::BufferNotInRAM) => { + trace!("Copying SPIS tx buffer into RAM for DMA"); + let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; + tx_ram_buf.copy_from_slice(tx); + self.blocking_inner_from_ram(rx, tx_ram_buf) + } + Err(error) => Err(error), + } + } + + async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(usize, usize), Error> { + let r = T::regs(); + let s = T::state(); + + // Clear status register. + r.status.write(|w| w.overflow().clear().overread().clear()); + + // Acquire semaphore. + if r.semstat.read().bits() != 1 { + // Reset and enable the acquire event. + r.events_acquired.reset(); + r.intenset.write(|w| w.acquired().set()); + + // Request acquiring the SPIS semaphore. + r.tasks_acquire.write(|w| unsafe { w.bits(1) }); + + // Wait until CPU has acquired the semaphore. + poll_fn(|cx| { + s.waker.register(cx.waker()); + if r.events_acquired.read().bits() == 1 { + r.events_acquired.reset(); + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + } + + self.prepare(rx, tx)?; + + // Wait for 'end' event. + r.intenset.write(|w| w.end().set()); + poll_fn(|cx| { + s.waker.register(cx.waker()); + if r.events_end.read().bits() != 0 { + r.events_end.reset(); + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + let n_rx = r.rxd.amount.read().bits() as usize; + let n_tx = r.txd.amount.read().bits() as usize; + + compiler_fence(Ordering::SeqCst); + + Ok((n_rx, n_tx)) + } + + async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { + match self.async_inner_from_ram(rx, tx).await { + Ok(n) => Ok(n), + Err(Error::BufferNotInRAM) => { + trace!("Copying SPIS tx buffer into RAM for DMA"); + let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; + tx_ram_buf.copy_from_slice(tx); + self.async_inner_from_ram(rx, tx_ram_buf).await + } + Err(error) => Err(error), + } + } + + /// Reads data from the SPI bus without sending anything. Blocks until `cs` is deasserted. + /// Returns number of bytes read. + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result { + self.blocking_inner(data, &[]).map(|n| n.0) + } + + /// Simultaneously sends and receives data. Blocks until the transmission is completed. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.blocking_inner(read, write) + } + + /// Same as [`blocking_transfer`](Spis::blocking_transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub fn blocking_transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.blocking_inner_from_ram(read, write) + } + + /// Simultaneously sends and receives data. + /// Places the received data into the same buffer and blocks until the transmission is completed. + /// Returns number of bytes transferred. + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result { + self.blocking_inner_from_ram(data, data).map(|n| n.0) + } + + /// Sends data, discarding any received data. Blocks until the transmission is completed. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes written. + pub fn blocking_write(&mut self, data: &[u8]) -> Result { + self.blocking_inner(&mut [], data).map(|n| n.1) + } + + /// Same as [`blocking_write`](Spis::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes written. + pub fn blocking_write_from_ram(&mut self, data: &[u8]) -> Result { + self.blocking_inner_from_ram(&mut [], data).map(|n| n.1) + } + + /// Reads data from the SPI bus without sending anything. + /// Returns number of bytes read. + pub async fn read(&mut self, data: &mut [u8]) -> Result { + self.async_inner(data, &[]).await.map(|n| n.0) + } + + /// Simultaneously sends and receives data. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.async_inner(read, write).await + } + + /// Same as [`transfer`](Spis::transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub async fn transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.async_inner_from_ram(read, write).await + } + + /// Simultaneously sends and receives data. Places the received data into the same buffer. + /// Returns number of bytes transferred. + pub async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result { + self.async_inner_from_ram(data, data).await.map(|n| n.0) + } + + /// Sends data, discarding any received data. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes written. + pub async fn write(&mut self, data: &[u8]) -> Result { + self.async_inner(&mut [], data).await.map(|n| n.1) + } + + /// Same as [`write`](Spis::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes written. + pub async fn write_from_ram(&mut self, data: &[u8]) -> Result { + self.async_inner_from_ram(&mut [], data).await.map(|n| n.1) + } + + /// Checks if last transaction overread. + pub fn is_overread(&mut self) -> bool { + T::regs().status.read().overread().is_present() + } + + /// Checks if last transaction overflowed. + pub fn is_overflow(&mut self) -> bool { + T::regs().status.read().overflow().is_present() + } +} + +impl<'d, T: Instance> Drop for Spis<'d, T> { + fn drop(&mut self) { + trace!("spis drop"); + + // Disable + let r = T::regs(); + r.enable.write(|w| w.enable().disabled()); + + gpio::deconfigure_pin(r.psel.sck.read().bits()); + gpio::deconfigure_pin(r.psel.csn.read().bits()); + gpio::deconfigure_pin(r.psel.miso.read().bits()); + gpio::deconfigure_pin(r.psel.mosi.read().bits()); + + trace!("spis drop: done"); + } +} + +pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + use super::*; + + pub struct State { + pub waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static pac::spis0::RegisterBlock; + fn state() -> &'static State; + } +} + +/// SPIS peripheral instance +pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +macro_rules! impl_spis { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::spis::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::spis0::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::spis::sealed::State { + static STATE: crate::spis::sealed::State = crate::spis::sealed::State::new(); + &STATE + } + } + impl crate::spis::Instance for peripherals::$type { + type Interrupt = crate::interrupt::$irq; + } + }; +} + +// ==================== + +impl<'d, T: Instance> SetConfig for Spis<'d, T> { + type Config = Config; + fn set_config(&mut self, config: &Self::Config) { + let r = T::regs(); + // Configure mode. + let mode = config.mode; + r.config.write(|w| { + match mode { + MODE_0 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().leading(); + } + MODE_1 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().trailing(); + } + MODE_2 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().leading(); + } + MODE_3 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().trailing(); + } + } + + w + }); + + // Set over-read character. + let orc = config.orc; + r.orc.write(|w| unsafe { w.orc().bits(orc) }); + + // Set default character. + let def = config.def; + r.def.write(|w| unsafe { w.def().bits(def) }); + + // Configure auto-acquire on 'transfer end' event. + let auto_acquire = config.auto_acquire; + r.shorts.write(|w| w.end_acquire().bit(auto_acquire)); + } +} diff --git a/embassy-nrf/src/temp.rs b/embassy-nrf/src/temp.rs index 7a7f61b51..5298faabc 100644 --- a/embassy-nrf/src/temp.rs +++ b/embassy-nrf/src/temp.rs @@ -1,4 +1,4 @@ -//! Temperature sensor interface. +//! Builtin temperature sensor driver. use core::future::poll_fn; use core::task::Poll; @@ -12,7 +12,7 @@ use crate::interrupt::InterruptExt; use crate::peripherals::TEMP; use crate::{interrupt, pac, Peripheral}; -/// Integrated temperature sensor. +/// Builtin temperature sensor driver. pub struct Temp<'d> { _irq: PeripheralRef<'d, interrupt::TEMP>, } @@ -20,6 +20,7 @@ pub struct Temp<'d> { static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d> Temp<'d> { + /// Create a new temperature sensor driver. pub fn new(_t: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { into_ref!(_t, irq); diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index c32a44637..bc2c8a3c1 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -243,22 +243,25 @@ impl Driver for RtcDriver { }) } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { critical_section::with(|cs| { let n = alarm.id() as _; let alarm = self.get_alarm(cs, alarm); alarm.timestamp.set(timestamp); - let t = self.now(); - - // If alarm timestamp has passed, trigger it instantly. - if timestamp <= t { - self.trigger_alarm(n, cs); - return; - } - let r = rtc(); + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); + + alarm.timestamp.set(u64::MAX); + + return false; + } + // If it hasn't triggered yet, setup it in the compare channel. // Write the CC value regardless of whether we're going to enable it now or not. @@ -287,6 +290,8 @@ impl Driver for RtcDriver { // It will be setup later by `next_period`. r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); } + + true }) } } diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs index bc8710640..d1ae57237 100644 --- a/embassy-nrf/src/timer.rs +++ b/embassy-nrf/src/timer.rs @@ -1,3 +1,9 @@ +//! Timer driver. +//! +//! Important note! This driver is very low level. For most time-related use cases, like +//! "sleep for X seconds", "do something every X seconds", or measuring time, you should +//! use [`embassy-time`](https://crates.io/crates/embassy-time) instead! + #![macro_use] use core::future::poll_fn; @@ -28,9 +34,13 @@ pub(crate) mod sealed { pub trait TimerType {} } +/// Basic Timer instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } + +/// Extended timer instance. pub trait ExtendedInstance: Instance + sealed::ExtendedInstance {} macro_rules! impl_timer { @@ -61,18 +71,28 @@ macro_rules! impl_timer { }; } +/// Timer frequency #[repr(u8)] pub enum Frequency { - // I'd prefer not to prefix these with `F`, but Rust identifiers can't start with digits. + /// 16MHz F16MHz = 0, + /// 8MHz F8MHz = 1, + /// 4MHz F4MHz = 2, + /// 2MHz F2MHz = 3, + /// 1MHz F1MHz = 4, + /// 500kHz F500kHz = 5, + /// 250kHz F250kHz = 6, + /// 125kHz F125kHz = 7, + /// 62500Hz F62500Hz = 8, + /// 31250Hz F31250Hz = 9, } @@ -86,7 +106,10 @@ pub enum Frequency { pub trait TimerType: sealed::TimerType {} +/// Marker type indicating the timer driver can await expiration (it owns the timer interrupt). pub enum Awaitable {} + +/// Marker type indicating the timer driver cannot await expiration (it does not own the timer interrupt). pub enum NotAwaitable {} impl sealed::TimerType for Awaitable {} @@ -94,12 +117,14 @@ impl sealed::TimerType for NotAwaitable {} impl TimerType for Awaitable {} impl TimerType for NotAwaitable {} +/// Timer driver. pub struct Timer<'d, T: Instance, I: TimerType = NotAwaitable> { _p: PeripheralRef<'d, T>, _i: PhantomData, } impl<'d, T: Instance> Timer<'d, T, Awaitable> { + /// Create a new async-capable timer driver. pub fn new_awaitable(timer: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { into_ref!(irq); @@ -107,16 +132,17 @@ impl<'d, T: Instance> Timer<'d, T, Awaitable> { irq.unpend(); irq.enable(); - Self::new_irqless(timer) + Self::new_inner(timer) } } + impl<'d, T: Instance> Timer<'d, T, NotAwaitable> { - /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. + /// Create a `Timer` driver without an interrupt, meaning `Cc::wait` won't work. /// /// This can be useful for triggering tasks via PPI /// `Uarte` uses this internally. pub fn new(timer: impl Peripheral

+ 'd) -> Self { - Self::new_irqless(timer) + Self::new_inner(timer) } } @@ -124,7 +150,7 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. /// /// This is used by the public constructors. - fn new_irqless(timer: impl Peripheral

+ 'd) -> Self { + fn new_inner(timer: impl Peripheral

+ 'd) -> Self { into_ref!(timer); let regs = T::regs(); diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index 8d6171fac..0dcb2b0da 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -1,11 +1,7 @@ +//! I2C-compatible Two Wire Interface in master mode (TWIM) driver. + #![macro_use] -//! HAL interface to the TWIM peripheral. -//! -//! See product specification: -//! -//! - nRF52832: Section 33 -//! - nRF52840: Section 6.31 use core::future::{poll_fn, Future}; use core::sync::atomic::compiler_fence; use core::sync::atomic::Ordering::SeqCst; @@ -23,22 +19,39 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::util::{slice_in_ram, slice_in_ram_or}; use crate::{gpio, pac, Peripheral}; +/// TWI frequency #[derive(Clone, Copy)] pub enum Frequency { - #[doc = "26738688: 100 kbps"] + /// 100 kbps K100 = 26738688, - #[doc = "67108864: 250 kbps"] + /// 250 kbps K250 = 67108864, - #[doc = "104857600: 400 kbps"] + /// 400 kbps K400 = 104857600, } +/// TWIM config. #[non_exhaustive] pub struct Config { + /// Frequency pub frequency: Frequency, + + /// Enable high drive for the SDA line. pub sda_high_drive: bool, + + /// Enable internal pullup for the SDA line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub sda_pullup: bool, + + /// Enable high drive for the SCL line. pub scl_high_drive: bool, + + /// Enable internal pullup for the SCL line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub scl_pullup: bool, } @@ -54,29 +67,38 @@ impl Default for Config { } } +/// TWI error. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// RX buffer was too long. RxBufferTooLong, + /// Data transmit failed. Transmit, + /// Data reception failed. Receive, - DMABufferNotInDataMemory, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Didn't receive an ACK bit after the address byte. Address might be wrong, or the i2c device chip might not be connected properly. AddressNack, + /// Didn't receive an ACK bit after a data byte. DataNack, + /// Overrun error. Overrun, + /// Timeout error. Timeout, } -/// Interface to a TWIM instance using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// TWI driver. pub struct Twim<'d, T: Instance> { _p: PeripheralRef<'d, T>, } impl<'d, T: Instance> Twim<'d, T> { + /// Create a new TWI driver. pub fn new( twim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -153,7 +175,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// Set TX buffer, checking that it is in RAM and has suitable length. unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::TxBufferTooLong); @@ -233,7 +255,7 @@ impl<'d, T: Instance> Twim<'d, T> { return Err(Error::DataNack); } if err.overrun().is_received() { - return Err(Error::DataNack); + return Err(Error::Overrun); } Ok(()) } @@ -435,7 +457,7 @@ impl<'d, T: Instance> Twim<'d, T> { ) -> Result<(), Error> { match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying TWIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; tx_ram_buf.copy_from_slice(wr_buffer); @@ -448,7 +470,7 @@ impl<'d, T: Instance> Twim<'d, T> { fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { match self.setup_write_from_ram(address, wr_buffer, inten) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying TWIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; tx_ram_buf.copy_from_slice(wr_buffer); @@ -612,6 +634,10 @@ impl<'d, T: Instance> Twim<'d, T> { // =========================================== + /// Read from an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { self.setup_read(address, buffer, true)?; self.async_wait().await; @@ -621,6 +647,10 @@ impl<'d, T: Instance> Twim<'d, T> { Ok(()) } + /// Write to an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { self.setup_write(address, buffer, true)?; self.async_wait().await; @@ -640,6 +670,11 @@ impl<'d, T: Instance> Twim<'d, T> { Ok(()) } + /// Write data to an I2C slave, then read data from the slave without + /// triggering a stop condition between the two. + /// + /// The buffers must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { self.setup_write_read(address, wr_buffer, rd_buffer, true)?; self.async_wait().await; @@ -705,7 +740,9 @@ pub(crate) mod sealed { } } +/// TWIM peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } @@ -776,7 +813,7 @@ mod eh1 { Self::RxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other, Self::Transmit => embedded_hal_1::i2c::ErrorKind::Other, Self::Receive => embedded_hal_1::i2c::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::i2c::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::i2c::ErrorKind::Other, Self::AddressNack => { embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) } @@ -841,39 +878,31 @@ mod eh1 { mod eha { use super::*; impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(address, buffer) + async fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Result<(), Error> { + self.read(address, buffer).await } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(address, bytes) + async fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Result<(), Error> { + self.write(address, bytes).await } - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write_read<'a>( + async fn write_read<'a>( &'a mut self, address: u8, wr_buffer: &'a [u8], rd_buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - self.write_read(address, wr_buffer, rd_buffer) + ) -> Result<(), Error> { + self.write_read(address, wr_buffer, rd_buffer).await } - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( + async fn transaction<'a, 'b>( &'a mut self, address: u8, operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { + ) -> Result<(), Error> { let _ = address; let _ = operations; - async move { todo!() } + todo!() } } } diff --git a/embassy-nrf/src/twis.rs b/embassy-nrf/src/twis.rs new file mode 100644 index 000000000..c514d9f2f --- /dev/null +++ b/embassy-nrf/src/twis.rs @@ -0,0 +1,792 @@ +//! I2C-compatible Two Wire Interface in slave mode (TWIM) driver. + +#![macro_use] + +use core::future::{poll_fn, Future}; +use core::sync::atomic::compiler_fence; +use core::sync::atomic::Ordering::SeqCst; +use core::task::Poll; + +use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +#[cfg(feature = "time")] +use embassy_time::{Duration, Instant}; + +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::gpio::Pin as GpioPin; +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::util::slice_in_ram_or; +use crate::{gpio, pac, Peripheral}; + +/// TWIS config. +#[non_exhaustive] +pub struct Config { + /// First address + pub address0: u8, + + /// Second address, optional. + pub address1: Option, + + /// Overread character. + /// + /// If the master keeps clocking the bus after all the bytes in the TX buffer have + /// already been transmitted, this byte will be constantly transmitted. + pub orc: u8, + + /// Enable high drive for the SDA line. + pub sda_high_drive: bool, + + /// Enable internal pullup for the SDA line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. + pub sda_pullup: bool, + + /// Enable high drive for the SCL line. + pub scl_high_drive: bool, + + /// Enable internal pullup for the SCL line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. + pub scl_pullup: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + address0: 0x55, + address1: None, + orc: 0x00, + scl_high_drive: false, + sda_pullup: false, + sda_high_drive: false, + scl_pullup: false, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum Status { + Read, + Write, +} + +/// TWIS error. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// TX buffer was too long. + TxBufferTooLong, + /// RX buffer was too long. + RxBufferTooLong, + /// Didn't receive an ACK bit after a data byte. + DataNack, + /// Bus error. + Bus, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Overflow + Overflow, + /// Overread + OverRead, + /// Timeout + Timeout, +} + +/// Received command +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// Read + Read, + /// Write+read + WriteRead(usize), + /// Write + Write(usize), +} + +/// TWIS driver. +pub struct Twis<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Twis<'d, T> { + /// Create a new TWIS driver. + pub fn new( + twis: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + sda: impl Peripheral

+ 'd, + scl: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(twis, irq, sda, scl); + + let r = T::regs(); + + // Configure pins + sda.conf().write(|w| { + w.dir().input(); + w.input().connect(); + if config.sda_high_drive { + w.drive().h0d1(); + } else { + w.drive().s0d1(); + } + if config.sda_pullup { + w.pull().pullup(); + } + w + }); + scl.conf().write(|w| { + w.dir().input(); + w.input().connect(); + if config.scl_high_drive { + w.drive().h0d1(); + } else { + w.drive().s0d1(); + } + if config.scl_pullup { + w.pull().pullup(); + } + w + }); + + // Select pins. + r.psel.sda.write(|w| unsafe { w.bits(sda.psel_bits()) }); + r.psel.scl.write(|w| unsafe { w.bits(scl.psel_bits()) }); + + // Enable TWIS instance. + r.enable.write(|w| w.enable().enabled()); + + // Disable all events interrupts + r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + // Set address + r.address[0].write(|w| unsafe { w.address().bits(config.address0) }); + r.config.write(|w| w.address0().enabled()); + if let Some(address1) = config.address1 { + r.address[1].write(|w| unsafe { w.address().bits(address1) }); + r.config.modify(|_r, w| w.address1().enabled()); + } + + // Set over-read character + r.orc.write(|w| unsafe { w.orc().bits(config.orc) }); + + // Generate suspend on read event + r.shorts.write(|w| w.read_suspend().enabled()); + + irq.set_handler(Self::on_interrupt); + irq.unpend(); + irq.enable(); + + Self { _p: twis } + } + + fn on_interrupt(_: *mut ()) { + let r = T::regs(); + let s = T::state(); + + if r.events_read.read().bits() != 0 || r.events_write.read().bits() != 0 { + s.waker.wake(); + r.intenclr.modify(|_r, w| w.read().clear().write().clear()); + } + if r.events_stopped.read().bits() != 0 { + s.waker.wake(); + r.intenclr.modify(|_r, w| w.stopped().clear()); + } + if r.events_error.read().bits() != 0 { + s.waker.wake(); + r.intenclr.modify(|_r, w| w.error().clear()); + } + } + + /// Set TX buffer, checking that it is in RAM and has suitable length. + unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::TxBufferTooLong); + } + + let r = T::regs(); + + r.txd.ptr.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + // + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + w.ptr().bits(buffer.as_ptr() as u32)); + r.txd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to `u8` is also fine. + // + // The MAXCNT field is 8 bits wide and accepts the full range of + // values. + w.maxcnt().bits(buffer.len() as _)); + + Ok(()) + } + + /// Set RX buffer, checking that it has suitable length. + unsafe fn set_rx_buffer(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // NOTE: RAM slice check is not necessary, as a mutable + // slice can only be built from data located in RAM. + + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::RxBufferTooLong); + } + + let r = T::regs(); + + r.rxd.ptr.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + // + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + w.ptr().bits(buffer.as_mut_ptr() as u32)); + r.rxd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to the type of maxcnt + // is also fine. + // + // Note that that nrf52840 maxcnt is a wider + // type than a u8, so we use a `_` cast rather than a `u8` cast. + // The MAXCNT field is thus at least 8 bits wide and accepts the + // full range of values that fit in a `u8`. + w.maxcnt().bits(buffer.len() as _)); + + Ok(()) + } + + fn clear_errorsrc(&mut self) { + let r = T::regs(); + r.errorsrc + .write(|w| w.overflow().bit(true).overread().bit(true).dnack().bit(true)); + } + + /// Returns matched address for latest command. + pub fn address_match(&self) -> u8 { + let r = T::regs(); + r.address[r.match_.read().bits() as usize].read().address().bits() + } + + /// Returns the index of the address matched in the latest command. + pub fn address_match_index(&self) -> usize { + T::regs().match_.read().bits() as _ + } + + /// Wait for read, write, stop or error + fn blocking_listen_wait(&mut self) -> Result { + let r = T::regs(); + loop { + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + while r.events_stopped.read().bits() == 0 {} + return Err(Error::Overflow); + } + if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return Err(Error::Bus); + } + if r.events_read.read().bits() != 0 { + r.events_read.reset(); + return Ok(Status::Read); + } + if r.events_write.read().bits() != 0 { + r.events_write.reset(); + return Ok(Status::Write); + } + } + } + + /// Wait for stop, repeated start or error + fn blocking_listen_wait_end(&mut self, status: Status) -> Result { + let r = T::regs(); + loop { + // stop if an error occured + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Overflow); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return match status { + Status::Read => Ok(Command::Read), + Status::Write => { + let n = r.rxd.amount.read().bits() as usize; + Ok(Command::Write(n)) + } + }; + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + let n = r.rxd.amount.read().bits() as usize; + return Ok(Command::WriteRead(n)); + } + } + } + + /// Wait for stop or error + fn blocking_wait(&mut self) -> Result { + let r = T::regs(); + loop { + // stop if an error occured + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + let errorsrc = r.errorsrc.read(); + if errorsrc.overread().is_detected() { + return Err(Error::OverRead); + } else if errorsrc.dnack().is_received() { + return Err(Error::DataNack); + } else { + return Err(Error::Bus); + } + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + let n = r.txd.amount.read().bits() as usize; + return Ok(n); + } + } + } + + /// Wait for stop or error with timeout + #[cfg(feature = "time")] + fn blocking_wait_timeout(&mut self, timeout: Duration) -> Result { + let r = T::regs(); + let deadline = Instant::now() + timeout; + loop { + // stop if an error occured + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + let errorsrc = r.errorsrc.read(); + if errorsrc.overread().is_detected() { + return Err(Error::OverRead); + } else if errorsrc.dnack().is_received() { + return Err(Error::DataNack); + } else { + return Err(Error::Bus); + } + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + let n = r.txd.amount.read().bits() as usize; + return Ok(n); + } else if Instant::now() > deadline { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Timeout); + } + } + } + + /// Wait for read, write, stop or error with timeout + #[cfg(feature = "time")] + fn blocking_listen_wait_timeout(&mut self, timeout: Duration) -> Result { + let r = T::regs(); + let deadline = Instant::now() + timeout; + loop { + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + while r.events_stopped.read().bits() == 0 {} + return Err(Error::Overflow); + } + if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return Err(Error::Bus); + } + if r.events_read.read().bits() != 0 { + r.events_read.reset(); + return Ok(Status::Read); + } + if r.events_write.read().bits() != 0 { + r.events_write.reset(); + return Ok(Status::Write); + } + if Instant::now() > deadline { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Timeout); + } + } + } + + /// Wait for stop, repeated start or error with timeout + #[cfg(feature = "time")] + fn blocking_listen_wait_end_timeout(&mut self, status: Status, timeout: Duration) -> Result { + let r = T::regs(); + let deadline = Instant::now() + timeout; + loop { + // stop if an error occured + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Overflow); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return match status { + Status::Read => Ok(Command::Read), + Status::Write => { + let n = r.rxd.amount.read().bits() as usize; + Ok(Command::Write(n)) + } + }; + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + let n = r.rxd.amount.read().bits() as usize; + return Ok(Command::WriteRead(n)); + } else if Instant::now() > deadline { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Timeout); + } + } + } + + /// Wait for stop or error + fn async_wait(&mut self) -> impl Future> { + poll_fn(move |cx| { + let r = T::regs(); + let s = T::state(); + + s.waker.register(cx.waker()); + + // stop if an error occured + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + let errorsrc = r.errorsrc.read(); + if errorsrc.overread().is_detected() { + return Poll::Ready(Err(Error::OverRead)); + } else if errorsrc.dnack().is_received() { + return Poll::Ready(Err(Error::DataNack)); + } else { + return Poll::Ready(Err(Error::Bus)); + } + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + let n = r.txd.amount.read().bits() as usize; + return Poll::Ready(Ok(n)); + } + + Poll::Pending + }) + } + + /// Wait for read or write + fn async_listen_wait(&mut self) -> impl Future> { + poll_fn(move |cx| { + let r = T::regs(); + let s = T::state(); + + s.waker.register(cx.waker()); + + // stop if an error occured + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Poll::Ready(Err(Error::Overflow)); + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + return Poll::Ready(Ok(Status::Read)); + } else if r.events_write.read().bits() != 0 { + r.events_write.reset(); + return Poll::Ready(Ok(Status::Write)); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return Poll::Ready(Err(Error::Bus)); + } + Poll::Pending + }) + } + + /// Wait for stop, repeated start or error + fn async_listen_wait_end(&mut self, status: Status) -> impl Future> { + poll_fn(move |cx| { + let r = T::regs(); + let s = T::state(); + + s.waker.register(cx.waker()); + + // stop if an error occured + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Poll::Ready(Err(Error::Overflow)); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return match status { + Status::Read => Poll::Ready(Ok(Command::Read)), + Status::Write => { + let n = r.rxd.amount.read().bits() as usize; + Poll::Ready(Ok(Command::Write(n))) + } + }; + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + let n = r.rxd.amount.read().bits() as usize; + return Poll::Ready(Ok(Command::WriteRead(n))); + } + Poll::Pending + }) + } + + fn setup_respond_from_ram(&mut self, buffer: &[u8], inten: bool) -> Result<(), Error> { + let r = T::regs(); + + compiler_fence(SeqCst); + + // Set up the DMA write. + unsafe { self.set_tx_buffer(buffer)? }; + + // Clear events + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + if inten { + r.intenset.write(|w| w.stopped().set().error().set()); + } else { + r.intenclr.write(|w| w.stopped().clear().error().clear()); + } + + // Start write operation. + r.tasks_preparetx.write(|w| unsafe { w.bits(1) }); + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + Ok(()) + } + + fn setup_respond(&mut self, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { + match self.setup_respond_from_ram(wr_buffer, inten) { + Ok(_) => Ok(()), + Err(Error::BufferNotInRAM) => { + trace!("Copying TWIS tx buffer into RAM for DMA"); + let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; + tx_ram_buf.copy_from_slice(wr_buffer); + self.setup_respond_from_ram(&tx_ram_buf, inten) + } + Err(error) => Err(error), + } + } + + fn setup_listen(&mut self, buffer: &mut [u8], inten: bool) -> Result<(), Error> { + let r = T::regs(); + compiler_fence(SeqCst); + + // Set up the DMA read. + unsafe { self.set_rx_buffer(buffer)? }; + + // Clear events + r.events_read.reset(); + r.events_write.reset(); + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + if inten { + r.intenset + .write(|w| w.stopped().set().error().set().read().set().write().set()); + } else { + r.intenclr + .write(|w| w.stopped().clear().error().clear().read().clear().write().clear()); + } + + // Start read operation. + r.tasks_preparerx.write(|w| unsafe { w.bits(1) }); + + Ok(()) + } + + fn setup_listen_end(&mut self, inten: bool) -> Result<(), Error> { + let r = T::regs(); + compiler_fence(SeqCst); + + // Clear events + r.events_read.reset(); + r.events_write.reset(); + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + if inten { + r.intenset.write(|w| w.stopped().set().error().set().read().set()); + } else { + r.intenclr.write(|w| w.stopped().clear().error().clear().read().clear()); + } + + Ok(()) + } + + /// Wait for commands from an I2C master. + /// `buffer` is provided in case master does a 'write' and is unused for 'read'. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// To know which one of the addresses were matched, call `address_match` or `address_match_index` + pub fn blocking_listen(&mut self, buffer: &mut [u8]) -> Result { + self.setup_listen(buffer, false)?; + let status = self.blocking_listen_wait()?; + if status == Status::Write { + self.setup_listen_end(false)?; + let command = self.blocking_listen_wait_end(status)?; + return Ok(command); + } + Ok(Command::Read) + } + + /// Respond to an I2C master READ command. + /// Returns the number of bytes written. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn blocking_respond_to_read(&mut self, buffer: &[u8]) -> Result { + self.setup_respond(buffer, false)?; + self.blocking_wait() + } + + /// Same as [`blocking_respond_to_read`](Twis::blocking_respond_to_read) but will fail instead of copying data into RAM. + /// Consult the module level documentation to learn more. + pub fn blocking_respond_to_read_from_ram(&mut self, buffer: &[u8]) -> Result { + self.setup_respond_from_ram(buffer, false)?; + self.blocking_wait() + } + + // =========================================== + + /// Wait for commands from an I2C master, with timeout. + /// `buffer` is provided in case master does a 'write' and is unused for 'read'. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// To know which one of the addresses were matched, call `address_match` or `address_match_index` + #[cfg(feature = "time")] + pub fn blocking_listen_timeout(&mut self, buffer: &mut [u8], timeout: Duration) -> Result { + self.setup_listen(buffer, false)?; + let status = self.blocking_listen_wait_timeout(timeout)?; + if status == Status::Write { + self.setup_listen_end(false)?; + let command = self.blocking_listen_wait_end_timeout(status, timeout)?; + return Ok(command); + } + Ok(Command::Read) + } + + /// Respond to an I2C master READ command with timeout. + /// Returns the number of bytes written. + /// See [`blocking_respond_to_read`]. + #[cfg(feature = "time")] + pub fn blocking_respond_to_read_timeout(&mut self, buffer: &[u8], timeout: Duration) -> Result { + self.setup_respond(buffer, false)?; + self.blocking_wait_timeout(timeout) + } + + /// Same as [`blocking_respond_to_read_timeout`](Twis::blocking_respond_to_read_timeout) but will fail instead of copying data into RAM. + /// Consult the module level documentation to learn more. + #[cfg(feature = "time")] + pub fn blocking_respond_to_read_from_ram_timeout( + &mut self, + buffer: &[u8], + timeout: Duration, + ) -> Result { + self.setup_respond_from_ram(buffer, false)?; + self.blocking_wait_timeout(timeout) + } + + // =========================================== + + /// Wait asynchronously for commands from an I2C master. + /// `buffer` is provided in case master does a 'write' and is unused for 'read'. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// To know which one of the addresses were matched, call `address_match` or `address_match_index` + pub async fn listen(&mut self, buffer: &mut [u8]) -> Result { + self.setup_listen(buffer, true)?; + let status = self.async_listen_wait().await?; + if status == Status::Write { + self.setup_listen_end(true)?; + let command = self.async_listen_wait_end(status).await?; + return Ok(command); + } + Ok(Command::Read) + } + + /// Respond to an I2C master READ command, asynchronously. + /// Returns the number of bytes written. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result { + self.setup_respond(buffer, true)?; + self.async_wait().await + } + + /// Same as [`respond_to_read`](Twis::respond_to_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub async fn respond_to_read_from_ram(&mut self, buffer: &[u8]) -> Result { + self.setup_respond_from_ram(buffer, true)?; + self.async_wait().await + } +} + +impl<'a, T: Instance> Drop for Twis<'a, T> { + fn drop(&mut self) { + trace!("twis drop"); + + // TODO: check for abort + + // disable! + let r = T::regs(); + r.enable.write(|w| w.enable().disabled()); + + gpio::deconfigure_pin(r.psel.sda.read().bits()); + gpio::deconfigure_pin(r.psel.scl.read().bits()); + + trace!("twis drop: done"); + } +} + +pub(crate) mod sealed { + use super::*; + + pub struct State { + pub waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static pac::twis0::RegisterBlock; + fn state() -> &'static State; + } +} + +/// TWIS peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +macro_rules! impl_twis { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::twis::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::twis0::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::twis::sealed::State { + static STATE: crate::twis::sealed::State = crate::twis::sealed::State::new(); + &STATE + } + } + impl crate::twis::Instance for peripherals::$type { + type Interrupt = crate::interrupt::$irq; + } + }; +} diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index d99599112..48457744b 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -1,8 +1,6 @@ -#![macro_use] - -//! Async UART +//! Universal Asynchronous Receiver Transmitter (UART) driver. //! -//! Async UART is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte]. +//! The UART driver is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte]. //! The [Uarte] here is useful for those use-cases where reading the UARTE peripheral is //! exclusively awaited on. If the [Uarte] is required to be awaited on with some other future, //! for example when using `futures_util::future::select`, then you should consider @@ -13,6 +11,8 @@ //! memory may be used given that buffers are passed in directly to its read and write //! methods. +#![macro_use] + use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -32,10 +32,13 @@ use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::util::slice_in_ram_or; use crate::{pac, Peripheral}; +/// UARTE config. #[derive(Clone)] #[non_exhaustive] pub struct Config { + /// Parity bit. pub parity: Parity, + /// Baud rate. pub baudrate: Baudrate, } @@ -48,32 +51,33 @@ impl Default for Config { } } +/// UART error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// Buffer was too long. BufferTooLong, - BufferZeroLength, - DMABufferNotInDataMemory, - // TODO: add other error variants. + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, } -/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// UARTE driver. pub struct Uarte<'d, T: Instance> { tx: UarteTx<'d, T>, rx: UarteRx<'d, T>, } -/// Transmitter interface to the UARTE peripheral obtained -/// via [Uarte]::split. +/// Transmitter part of the UARTE driver. +/// +/// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteTx<'d, T: Instance> { _p: PeripheralRef<'d, T>, } -/// Receiver interface to the UARTE peripheral obtained -/// via [Uarte]::split. +/// Receiver part of the UARTE driver. +/// +/// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteRx<'d, T: Instance> { _p: PeripheralRef<'d, T>, } @@ -166,13 +170,68 @@ impl<'d, T: Instance> Uarte<'d, T> { } } - /// Split the Uarte into a transmitter and receiver, which is - /// particuarly useful when having two tasks correlating to - /// transmitting and receiving. + /// Split the Uarte into the transmitter and receiver parts. + /// + /// This is useful to concurrently transmit and receive from independent tasks. pub fn split(self) -> (UarteTx<'d, T>, UarteRx<'d, T>) { (self.tx, self.rx) } + /// Split the Uarte into the transmitter and receiver with idle support parts. + /// + /// This is useful to concurrently transmit and receive from independent tasks. + pub fn split_with_idle( + self, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ) -> (UarteTx<'d, T>, UarteRxWithIdle<'d, T, U>) { + let mut timer = Timer::new(timer); + + into_ref!(ppi_ch1, ppi_ch2); + + let r = T::regs(); + + // BAUDRATE register values are `baudrate * 2^32 / 16000000` + // source: https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values + // + // We want to stop RX if line is idle for 2 bytes worth of time + // That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) + // This gives us the amount of 16M ticks for 20 bits. + let baudrate = r.baudrate.read().baudrate().variant().unwrap(); + let timeout = 0x8000_0000 / (baudrate as u32 / 40); + + timer.set_frequency(Frequency::F16MHz); + timer.cc(0).write(timeout); + timer.cc(0).short_compare_clear(); + timer.cc(0).short_compare_stop(); + + let mut ppi_ch1 = Ppi::new_one_to_two( + ppi_ch1.map_into(), + Event::from_reg(&r.events_rxdrdy), + timer.task_clear(), + timer.task_start(), + ); + ppi_ch1.enable(); + + let mut ppi_ch2 = Ppi::new_one_to_one( + ppi_ch2.map_into(), + timer.cc(0).event_compare(), + Task::from_reg(&r.tasks_stoprx), + ); + ppi_ch2.enable(); + + ( + self.tx, + UarteRxWithIdle { + rx: self.rx, + timer, + ppi_ch1: ppi_ch1, + _ppi_ch2: ppi_ch2, + }, + ) + } + /// Return the endtx event for use with PPI pub fn event_endtx(&self) -> Event { let r = T::regs(); @@ -193,10 +252,12 @@ impl<'d, T: Instance> Uarte<'d, T> { } } + /// Read bytes until the buffer is filled. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await } + /// Write all bytes in the buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.write(buffer).await } @@ -206,10 +267,12 @@ impl<'d, T: Instance> Uarte<'d, T> { self.tx.write_from_ram(buffer).await } + /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.blocking_read(buffer) } + /// Write all bytes in the buffer. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buffer) } @@ -301,10 +364,11 @@ impl<'d, T: Instance> UarteTx<'d, T> { Self { _p: uarte } } + /// Write all bytes in the buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { match self.write_from_ram(buffer).await { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying UARTE tx buffer into RAM for DMA"); let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; ram_buf.copy_from_slice(buffer); @@ -314,11 +378,13 @@ impl<'d, T: Instance> UarteTx<'d, T> { } } + /// Same as [`write`](Self::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub async fn write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } + + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); } @@ -368,10 +434,11 @@ impl<'d, T: Instance> UarteTx<'d, T> { Ok(()) } + /// Write all bytes in the buffer. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { match self.blocking_write_from_ram(buffer) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying UARTE tx buffer into RAM for DMA"); let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; ram_buf.copy_from_slice(buffer); @@ -381,11 +448,13 @@ impl<'d, T: Instance> UarteTx<'d, T> { } } + /// Same as [`write_from_ram`](Self::write_from_ram) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub fn blocking_write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } + + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); } @@ -493,9 +562,10 @@ impl<'d, T: Instance> UarteRx<'d, T> { Self { _p: uarte } } + /// Read bytes until the buffer is filled. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); @@ -546,9 +616,10 @@ impl<'d, T: Instance> UarteRx<'d, T> { Ok(()) } + /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); @@ -597,6 +668,128 @@ impl<'a, T: Instance> Drop for UarteRx<'a, T> { } } +/// Receiver part of the UARTE driver, with `read_until_idle` support. +/// +/// This can be obtained via [`Uarte::split_with_idle`]. +pub struct UarteRxWithIdle<'d, T: Instance, U: TimerInstance> { + rx: UarteRx<'d, T>, + timer: Timer<'d, U>, + ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 2>, + _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 1>, +} + +impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { + /// Read bytes until the buffer is filled. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.ppi_ch1.disable(); + self.rx.read(buffer).await + } + + /// Read bytes until the buffer is filled. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.ppi_ch1.disable(); + self.rx.blocking_read(buffer) + } + + /// Read bytes until the buffer is filled, or the line becomes idle. + /// + /// Returns the amount of bytes read. + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { + if buffer.len() == 0 { + return Ok(0); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let ptr = buffer.as_ptr(); + let len = buffer.len(); + + let r = T::regs(); + let s = T::state(); + + self.ppi_ch1.enable(); + + let drop = OnDrop::new(|| { + self.timer.stop(); + + r.intenclr.write(|w| w.endrx().clear()); + r.events_rxto.reset(); + r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + + while r.events_endrx.read().bits() == 0 {} + }); + + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + r.events_endrx.reset(); + r.intenset.write(|w| w.endrx().set()); + + compiler_fence(Ordering::SeqCst); + + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + + poll_fn(|cx| { + s.endrx_waker.register(cx.waker()); + if r.events_endrx.read().bits() != 0 { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + let n = r.rxd.amount.read().amount().bits() as usize; + + self.timer.stop(); + r.events_rxstarted.reset(); + + drop.defuse(); + + Ok(n) + } + + /// Read bytes until the buffer is filled, or the line becomes idle. + /// + /// Returns the amount of bytes read. + pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result { + if buffer.len() == 0 { + return Ok(0); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let ptr = buffer.as_ptr(); + let len = buffer.len(); + + let r = T::regs(); + + self.ppi_ch1.enable(); + + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + r.events_endrx.reset(); + r.intenclr.write(|w| w.endrx().clear()); + + compiler_fence(Ordering::SeqCst); + + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + + while r.events_endrx.read().bits() == 0 {} + + compiler_fence(Ordering::SeqCst); + let n = r.rxd.amount.read().amount().bits() as usize; + + self.timer.stop(); + r.events_rxstarted.reset(); + + Ok(n) + } +} + #[cfg(not(any(feature = "_nrf9160", feature = "nrf5340")))] pub(crate) fn apply_workaround_for_enable_anomaly(_r: &crate::pac::uarte0::RegisterBlock) { // Do nothing @@ -665,270 +858,6 @@ pub(crate) fn drop_tx_rx(r: &pac::uarte0::RegisterBlock, s: &sealed::State) { } } -/// Interface to an UARTE peripheral that uses an additional timer and two PPI channels, -/// allowing it to implement the ReadUntilIdle trait. -pub struct UarteWithIdle<'d, U: Instance, T: TimerInstance> { - tx: UarteTx<'d, U>, - rx: UarteRxWithIdle<'d, U, T>, -} - -impl<'d, U: Instance, T: TimerInstance> UarteWithIdle<'d, U, T> { - /// Create a new UARTE without hardware flow control - pub fn new( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

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

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

+ 'd, - cts: impl Peripheral

+ 'd, - rts: impl Peripheral

+ 'd, - config: Config, - ) -> Self { - into_ref!(rxd, txd, cts, rts); - Self::new_inner( - uarte, - timer, - ppi_ch1, - ppi_ch2, - irq, - rxd.map_into(), - txd.map_into(), - Some(cts.map_into()), - Some(rts.map_into()), - config, - ) - } - - fn new_inner( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - rxd: PeripheralRef<'d, AnyPin>, - txd: PeripheralRef<'d, AnyPin>, - cts: Option>, - rts: Option>, - config: Config, - ) -> Self { - let baudrate = config.baudrate; - let (tx, rx) = Uarte::new_inner(uarte, irq, rxd, txd, cts, rts, config).split(); - - let mut timer = Timer::new(timer); - - into_ref!(ppi_ch1, ppi_ch2); - - let r = U::regs(); - - // BAUDRATE register values are `baudrate * 2^32 / 16000000` - // source: https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values - // - // We want to stop RX if line is idle for 2 bytes worth of time - // That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) - // This gives us the amount of 16M ticks for 20 bits. - let timeout = 0x8000_0000 / (baudrate as u32 / 40); - - timer.set_frequency(Frequency::F16MHz); - timer.cc(0).write(timeout); - timer.cc(0).short_compare_clear(); - timer.cc(0).short_compare_stop(); - - let mut ppi_ch1 = Ppi::new_one_to_two( - ppi_ch1.map_into(), - Event::from_reg(&r.events_rxdrdy), - timer.task_clear(), - timer.task_start(), - ); - ppi_ch1.enable(); - - let mut ppi_ch2 = Ppi::new_one_to_one( - ppi_ch2.map_into(), - timer.cc(0).event_compare(), - Task::from_reg(&r.tasks_stoprx), - ); - ppi_ch2.enable(); - - Self { - tx, - rx: UarteRxWithIdle { - rx, - timer, - ppi_ch1: ppi_ch1, - _ppi_ch2: ppi_ch2, - }, - } - } - - /// Split the Uarte into a transmitter and receiver, which is - /// particuarly useful when having two tasks correlating to - /// transmitting and receiving. - pub fn split(self) -> (UarteTx<'d, U>, UarteRxWithIdle<'d, U, T>) { - (self.tx, self.rx) - } - - pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.rx.read(buffer).await - } - - pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { - self.tx.write(buffer).await - } - - pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.rx.blocking_read(buffer) - } - - pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - self.tx.blocking_write(buffer) - } - - pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - self.rx.read_until_idle(buffer).await - } - - pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - self.rx.blocking_read_until_idle(buffer) - } -} - -pub struct UarteRxWithIdle<'d, U: Instance, T: TimerInstance> { - rx: UarteRx<'d, U>, - timer: Timer<'d, T>, - ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 2>, - _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 1>, -} - -impl<'d, U: Instance, T: TimerInstance> UarteRxWithIdle<'d, U, T> { - pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.ppi_ch1.disable(); - self.rx.read(buffer).await - } - - pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.ppi_ch1.disable(); - self.rx.blocking_read(buffer) - } - - pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - if buffer.len() == 0 { - return Err(Error::BufferZeroLength); - } - if buffer.len() > EASY_DMA_SIZE { - return Err(Error::BufferTooLong); - } - - let ptr = buffer.as_ptr(); - let len = buffer.len(); - - let r = U::regs(); - let s = U::state(); - - self.ppi_ch1.enable(); - - let drop = OnDrop::new(|| { - self.timer.stop(); - - r.intenclr.write(|w| w.endrx().clear()); - r.events_rxto.reset(); - r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); - - while r.events_endrx.read().bits() == 0 {} - }); - - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); - - r.events_endrx.reset(); - r.intenset.write(|w| w.endrx().set()); - - compiler_fence(Ordering::SeqCst); - - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - - poll_fn(|cx| { - s.endrx_waker.register(cx.waker()); - if r.events_endrx.read().bits() != 0 { - return Poll::Ready(()); - } - Poll::Pending - }) - .await; - - compiler_fence(Ordering::SeqCst); - let n = r.rxd.amount.read().amount().bits() as usize; - - self.timer.stop(); - r.events_rxstarted.reset(); - - drop.defuse(); - - Ok(n) - } - - pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - if buffer.len() == 0 { - return Err(Error::BufferZeroLength); - } - if buffer.len() > EASY_DMA_SIZE { - return Err(Error::BufferTooLong); - } - - let ptr = buffer.as_ptr(); - let len = buffer.len(); - - let r = U::regs(); - - self.ppi_ch1.enable(); - - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); - - r.events_endrx.reset(); - r.intenclr.write(|w| w.endrx().clear()); - - compiler_fence(Ordering::SeqCst); - - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - - while r.events_endrx.read().bits() == 0 {} - - compiler_fence(Ordering::SeqCst); - let n = r.rxd.amount.read().amount().bits() as usize; - - self.timer.stop(); - r.events_rxstarted.reset(); - - Ok(n) - } -} pub(crate) mod sealed { use core::sync::atomic::AtomicU8; @@ -957,7 +886,9 @@ pub(crate) mod sealed { } } +/// UARTE peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } @@ -1006,18 +937,6 @@ mod eh02 { Ok(()) } } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_02::blocking::serial::Write for UarteWithIdle<'d, U, T> { - type Error = Error; - - fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn bflush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - } } #[cfg(feature = "unstable-traits")] @@ -1028,8 +947,7 @@ mod eh1 { fn kind(&self) -> embedded_hal_1::serial::ErrorKind { match *self { Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, - Self::BufferZeroLength => embedded_hal_1::serial::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::serial::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::serial::ErrorKind::Other, } } } @@ -1067,10 +985,6 @@ mod eh1 { impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteRx<'d, T> { type Error = Error; } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_1::serial::ErrorType for UarteWithIdle<'d, U, T> { - type Error = Error; - } } #[cfg(all( @@ -1100,7 +1014,7 @@ mod eha { type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { + fn flush(&mut self) -> Result<(), Self::Error> { async move { Ok(()) } } } @@ -1114,7 +1028,7 @@ mod eha { type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { + fn flush(&mut self) -> Result<(), Self::Error> { async move { Ok(()) } } } @@ -1144,7 +1058,7 @@ mod eha { type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { + fn flush(&mut self) -> Result<(), Self::Error> { async move { Ok(()) } } } diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 00da5c9dd..cd142f00f 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -1,6 +1,8 @@ +//! Universal Serial Bus (USB) driver. + #![macro_use] -use core::future::{poll_fn, Future}; +use core::future::poll_fn; use core::marker::PhantomData; use core::mem::MaybeUninit; use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; @@ -24,42 +26,38 @@ static EP_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); +/// Trait for detecting USB VBUS power. +/// /// There are multiple ways to detect USB power. The behavior /// here provides a hook into determining whether it is. -pub trait UsbSupply { +pub trait VbusDetect { + /// Report whether power is detected. + /// + /// This is indicated by the `USBREGSTATUS.VBUSDETECT` register, or the + /// `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral. fn is_usb_detected(&self) -> bool; - type UsbPowerReadyFuture<'a>: Future> + 'a - where - Self: 'a; - fn wait_power_ready(&mut self) -> Self::UsbPowerReadyFuture<'_>; + /// Wait until USB power is ready. + /// + /// USB power ready is indicated by the `USBREGSTATUS.OUTPUTRDY` register, or the + /// `USBPWRRDY` event from the `POWER` peripheral. + async fn wait_power_ready(&mut self) -> Result<(), ()>; } -pub struct Driver<'d, T: Instance, P: UsbSupply> { - _p: PeripheralRef<'d, T>, - alloc_in: Allocator, - alloc_out: Allocator, - usb_supply: P, -} - -/// Uses the POWER peripheral to detect when power is available -/// for USB. Unsuitable for usage with the nRF softdevice. +/// [`VbusDetect`] implementation using the native hardware POWER peripheral. +/// +/// Unsuitable for usage with the nRF softdevice, since it reserves exclusive acces +/// to POWER. In that case, use [`VbusDetectSignal`]. #[cfg(not(feature = "_nrf5340-app"))] -pub struct PowerUsb { +pub struct HardwareVbusDetect { _private: (), } -/// Can be used to signal that power is available. Particularly suited for -/// use with the nRF softdevice. -pub struct SignalledSupply { - usb_detected: AtomicBool, - power_ready: AtomicBool, -} - static POWER_WAKER: AtomicWaker = NEW_AW; #[cfg(not(feature = "_nrf5340-app"))] -impl PowerUsb { +impl HardwareVbusDetect { + /// Create a new `VbusDetectNative`. pub fn new(power_irq: impl Interrupt) -> Self { let regs = unsafe { &*pac::POWER::ptr() }; @@ -96,14 +94,13 @@ impl PowerUsb { } #[cfg(not(feature = "_nrf5340-app"))] -impl UsbSupply for PowerUsb { +impl VbusDetect for HardwareVbusDetect { fn is_usb_detected(&self) -> bool { let regs = unsafe { &*pac::POWER::ptr() }; regs.usbregstatus.read().vbusdetect().is_vbus_present() } - type UsbPowerReadyFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_power_ready(&mut self) -> Self::UsbPowerReadyFuture<'_> { + async fn wait_power_ready(&mut self) -> Result<(), ()> { poll_fn(move |cx| { POWER_WAKER.register(cx.waker()); let regs = unsafe { &*pac::POWER::ptr() }; @@ -116,10 +113,24 @@ impl UsbSupply for PowerUsb { Poll::Pending } }) + .await } } -impl SignalledSupply { +/// Software-backed [`VbusDetect`] implementation. +/// +/// This implementation does not interact with the hardware, it allows user code +/// to notify the power events by calling functions instead. +/// +/// This is suitable for use with the nRF softdevice, by calling the functions +/// when the softdevice reports power-related events. +pub struct SoftwareVbusDetect { + usb_detected: AtomicBool, + power_ready: AtomicBool, +} + +impl SoftwareVbusDetect { + /// Create a new `SoftwareVbusDetect`. pub fn new(usb_detected: bool, power_ready: bool) -> Self { BUS_WAKER.wake(); @@ -129,6 +140,9 @@ impl SignalledSupply { } } + /// Report whether power was detected. + /// + /// Equivalent to the `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral. pub fn detected(&self, detected: bool) { self.usb_detected.store(detected, Ordering::Relaxed); self.power_ready.store(false, Ordering::Relaxed); @@ -136,19 +150,21 @@ impl SignalledSupply { POWER_WAKER.wake(); } + /// Report when USB power is ready. + /// + /// Equivalent to the `USBPWRRDY` event from the `POWER` peripheral. pub fn ready(&self) { self.power_ready.store(true, Ordering::Relaxed); POWER_WAKER.wake(); } } -impl UsbSupply for &SignalledSupply { +impl VbusDetect for &SoftwareVbusDetect { fn is_usb_detected(&self) -> bool { self.usb_detected.load(Ordering::Relaxed) } - type UsbPowerReadyFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_power_ready(&mut self) -> Self::UsbPowerReadyFuture<'_> { + async fn wait_power_ready(&mut self) -> Result<(), ()> { poll_fn(move |cx| { POWER_WAKER.register(cx.waker()); @@ -160,10 +176,20 @@ impl UsbSupply for &SignalledSupply { Poll::Pending } }) + .await } } -impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> { +/// USB driver. +pub struct Driver<'d, T: Instance, P: VbusDetect> { + _p: PeripheralRef<'d, T>, + alloc_in: Allocator, + alloc_out: Allocator, + usb_supply: P, +} + +impl<'d, T: Instance, P: VbusDetect> Driver<'d, T, P> { + /// Create a new USB driver. pub fn new(usb: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, usb_supply: P) -> Self { into_ref!(usb, irq); irq.set_handler(Self::on_interrupt); @@ -229,7 +255,7 @@ impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> { } } -impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> { +impl<'d, T: Instance, P: VbusDetect + 'd> driver::Driver<'d> for Driver<'d, T, P> { type EndpointOut = Endpoint<'d, T, Out>; type EndpointIn = Endpoint<'d, T, In>; type ControlPipe = ControlPipe<'d, T>; @@ -239,7 +265,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_in.allocate(ep_type)?; let ep_addr = EndpointAddress::from_parts(index, Direction::In); @@ -247,7 +273,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } @@ -255,7 +281,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_out.allocate(ep_type)?; let ep_addr = EndpointAddress::from_parts(index, Direction::Out); @@ -263,7 +289,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } @@ -282,68 +308,60 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> } } -pub struct Bus<'d, T: Instance, P: UsbSupply> { +/// USB bus. +pub struct Bus<'d, T: Instance, P: VbusDetect> { _p: PeripheralRef<'d, T>, power_available: bool, usb_supply: P, } -impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { - type EnableFuture<'a> = impl Future + 'a where Self: 'a; - type DisableFuture<'a> = impl Future + 'a where Self: 'a; - type PollFuture<'a> = impl Future + 'a where Self: 'a; - type RemoteWakeupFuture<'a> = impl Future> + 'a where Self: 'a; +impl<'d, T: Instance, P: VbusDetect> driver::Bus for Bus<'d, T, P> { + async fn enable(&mut self) { + let regs = T::regs(); - fn enable(&mut self) -> Self::EnableFuture<'_> { - async move { - let regs = T::regs(); + errata::pre_enable(); - errata::pre_enable(); + regs.enable.write(|w| w.enable().enabled()); - regs.enable.write(|w| w.enable().enabled()); - - // Wait until the peripheral is ready. - regs.intenset.write(|w| w.usbevent().set_bit()); - poll_fn(|cx| { - BUS_WAKER.register(cx.waker()); - if regs.eventcause.read().ready().is_ready() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - regs.eventcause.write(|w| w.ready().set_bit()); // Write 1 to clear. - - errata::post_enable(); - - unsafe { NVIC::unmask(pac::Interrupt::USBD) }; - - regs.intenset.write(|w| { - w.usbreset().set_bit(); - w.usbevent().set_bit(); - w.epdata().set_bit(); - w - }); - - if self.usb_supply.wait_power_ready().await.is_ok() { - // Enable the USB pullup, allowing enumeration. - regs.usbpullup.write(|w| w.connect().enabled()); - trace!("enabled"); + // Wait until the peripheral is ready. + regs.intenset.write(|w| w.usbevent().set_bit()); + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + if regs.eventcause.read().ready().is_ready() { + Poll::Ready(()) } else { - trace!("usb power not ready due to usb removal"); + Poll::Pending } + }) + .await; + regs.eventcause.write(|w| w.ready().clear_bit_by_one()); + + errata::post_enable(); + + unsafe { NVIC::unmask(pac::Interrupt::USBD) }; + + regs.intenset.write(|w| { + w.usbreset().set_bit(); + w.usbevent().set_bit(); + w.epdata().set_bit(); + w + }); + + if self.usb_supply.wait_power_ready().await.is_ok() { + // Enable the USB pullup, allowing enumeration. + regs.usbpullup.write(|w| w.connect().enabled()); + trace!("enabled"); + } else { + trace!("usb power not ready due to usb removal"); } } - fn disable(&mut self) -> Self::DisableFuture<'_> { - async move { - let regs = T::regs(); - regs.enable.write(|x| x.enable().disabled()); - } + async fn disable(&mut self) { + let regs = T::regs(); + regs.enable.write(|x| x.enable().disabled()); } - fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { + async fn poll(&mut self) -> Event { poll_fn(move |cx| { BUS_WAKER.register(cx.waker()); let regs = T::regs(); @@ -367,24 +385,24 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { let r = regs.eventcause.read(); if r.isooutcrc().bit() { - regs.eventcause.write(|w| w.isooutcrc().set_bit()); + regs.eventcause.write(|w| w.isooutcrc().detected()); trace!("USB event: isooutcrc"); } if r.usbwuallowed().bit() { - regs.eventcause.write(|w| w.usbwuallowed().set_bit()); + regs.eventcause.write(|w| w.usbwuallowed().allowed()); trace!("USB event: usbwuallowed"); } if r.suspend().bit() { - regs.eventcause.write(|w| w.suspend().set_bit()); + regs.eventcause.write(|w| w.suspend().detected()); regs.lowpower.write(|w| w.lowpower().low_power()); return Poll::Ready(Event::Suspend); } if r.resume().bit() { - regs.eventcause.write(|w| w.resume().set_bit()); + regs.eventcause.write(|w| w.resume().detected()); return Poll::Ready(Event::Resume); } if r.ready().bit() { - regs.eventcause.write(|w| w.ready().set_bit()); + regs.eventcause.write(|w| w.ready().ready()); trace!("USB event: ready"); } @@ -401,11 +419,7 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { Poll::Pending }) - } - - #[inline] - fn set_address(&mut self, _addr: u8) { - // Nothing to do, the peripheral handles this. + .await } fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { @@ -493,46 +507,47 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { } #[inline] - fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> { - async move { - let regs = T::regs(); + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + let regs = T::regs(); - if regs.lowpower.read().lowpower().is_low_power() { - errata::pre_wakeup(); + if regs.lowpower.read().lowpower().is_low_power() { + errata::pre_wakeup(); - regs.lowpower.write(|w| w.lowpower().force_normal()); + regs.lowpower.write(|w| w.lowpower().force_normal()); - poll_fn(|cx| { - BUS_WAKER.register(cx.waker()); - let regs = T::regs(); - let r = regs.eventcause.read(); + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + let regs = T::regs(); + let r = regs.eventcause.read(); - if regs.events_usbreset.read().bits() != 0 { - Poll::Ready(()) - } else if r.resume().bit() { - Poll::Ready(()) - } else if r.usbwuallowed().bit() { - regs.eventcause.write(|w| w.usbwuallowed().set_bit()); + if regs.events_usbreset.read().bits() != 0 { + Poll::Ready(()) + } else if r.resume().bit() { + Poll::Ready(()) + } else if r.usbwuallowed().bit() { + regs.eventcause.write(|w| w.usbwuallowed().allowed()); - regs.dpdmvalue.write(|w| w.state().resume()); - regs.tasks_dpdmdrive.write(|w| w.tasks_dpdmdrive().set_bit()); + regs.dpdmvalue.write(|w| w.state().resume()); + regs.tasks_dpdmdrive.write(|w| w.tasks_dpdmdrive().set_bit()); - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; - errata::post_wakeup(); - } - - Ok(()) + errata::post_wakeup(); } + + Ok(()) } } +/// Type-level marker for OUT endpoints. pub enum Out {} + +/// Type-level marker for IN endpoints. pub enum In {} trait EndpointDir { @@ -575,6 +590,7 @@ impl EndpointDir for Out { } } +/// USB endpoint. pub struct Endpoint<'d, T: Instance, Dir> { _phantom: PhantomData<(&'d mut T, Dir)>, info: EndpointInfo, @@ -594,9 +610,7 @@ impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { + async fn wait_enabled(&mut self) { let i = self.info.addr.index(); assert!(i != 0); @@ -608,6 +622,7 @@ impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir Poll::Pending } }) + .await } } @@ -712,173 +727,155 @@ unsafe fn write_dma(i: usize, buf: &[u8]) { } impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; + async fn read(&mut self, buf: &mut [u8]) -> Result { + let i = self.info.addr.index(); + assert!(i != 0); - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let i = self.info.addr.index(); - assert!(i != 0); + self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; - self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; - - unsafe { read_dma::(i, buf) } - } + unsafe { read_dma::(i, buf) } } } impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + let i = self.info.addr.index(); + assert!(i != 0); - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - let i = self.info.addr.index(); - assert!(i != 0); + self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; - self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; + unsafe { write_dma::(i, buf) } - unsafe { write_dma::(i, buf) } - - Ok(()) - } + Ok(()) } } +/// USB control pipe. pub struct ControlPipe<'d, T: Instance> { _p: PeripheralRef<'d, T>, max_packet_size: u16, } impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { - type SetupFuture<'a> = impl Future + 'a where Self: 'a; - type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; - type DataInFuture<'a> = impl Future> + 'a where Self: 'a; - type AcceptFuture<'a> = impl Future + 'a where Self: 'a; - type RejectFuture<'a> = impl Future + 'a where Self: 'a; - fn max_packet_size(&self) -> usize { usize::from(self.max_packet_size) } - fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { - async move { + async fn setup(&mut self) -> [u8; 8] { + let regs = T::regs(); + + // Reset shorts + regs.shorts.write(|w| w); + + // Wait for SETUP packet + regs.intenset.write(|w| w.ep0setup().set()); + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); let regs = T::regs(); + if regs.events_ep0setup.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; - // Reset shorts - regs.shorts.write(|w| w); + regs.events_ep0setup.reset(); - // Wait for SETUP packet - regs.intenset.write(|w| w.ep0setup().set()); - poll_fn(|cx| { - EP0_WAKER.register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0setup.read().bits() != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; + let mut buf = [0; 8]; + buf[0] = regs.bmrequesttype.read().bits() as u8; + buf[1] = regs.brequest.read().brequest().bits(); + buf[2] = regs.wvaluel.read().wvaluel().bits(); + buf[3] = regs.wvalueh.read().wvalueh().bits(); + buf[4] = regs.windexl.read().windexl().bits(); + buf[5] = regs.windexh.read().windexh().bits(); + buf[6] = regs.wlengthl.read().wlengthl().bits(); + buf[7] = regs.wlengthh.read().wlengthh().bits(); - regs.events_ep0setup.reset(); - - let mut buf = [0; 8]; - buf[0] = regs.bmrequesttype.read().bits() as u8; - buf[1] = regs.brequest.read().brequest().bits(); - buf[2] = regs.wvaluel.read().wvaluel().bits(); - buf[3] = regs.wvalueh.read().wvalueh().bits(); - buf[4] = regs.windexl.read().windexl().bits(); - buf[5] = regs.windexh.read().windexh().bits(); - buf[6] = regs.wlengthl.read().wlengthl().bits(); - buf[7] = regs.wlengthh.read().wlengthh().bits(); - - buf - } + buf } - fn data_out<'a>(&'a mut self, buf: &'a mut [u8], _first: bool, _last: bool) -> Self::DataOutFuture<'a> { - async move { + async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { + let regs = T::regs(); + + regs.events_ep0datadone.reset(); + + // This starts a RX on EP0. events_ep0datadone notifies when done. + regs.tasks_ep0rcvout.write(|w| w.tasks_ep0rcvout().set_bit()); + + // Wait until ready + regs.intenset.write(|w| { + w.usbreset().set(); + w.ep0setup().set(); + w.ep0datadone().set() + }); + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); let regs = T::regs(); + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset.read().bits() != 0 { + trace!("aborted control data_out: usb reset"); + Poll::Ready(Err(EndpointError::Disabled)) + } else if regs.events_ep0setup.read().bits() != 0 { + trace!("aborted control data_out: received another SETUP"); + Poll::Ready(Err(EndpointError::Disabled)) + } else { + Poll::Pending + } + }) + .await?; - regs.events_ep0datadone.reset(); - - // This starts a RX on EP0. events_ep0datadone notifies when done. - regs.tasks_ep0rcvout.write(|w| w.tasks_ep0rcvout().set_bit()); - - // Wait until ready - regs.intenset.write(|w| { - w.usbreset().set(); - w.ep0setup().set(); - w.ep0datadone().set() - }); - poll_fn(|cx| { - EP0_WAKER.register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(Ok(())) - } else if regs.events_usbreset.read().bits() != 0 { - trace!("aborted control data_out: usb reset"); - Poll::Ready(Err(EndpointError::Disabled)) - } else if regs.events_ep0setup.read().bits() != 0 { - trace!("aborted control data_out: received another SETUP"); - Poll::Ready(Err(EndpointError::Disabled)) - } else { - Poll::Pending - } - }) - .await?; - - unsafe { read_dma::(0, buf) } - } + unsafe { read_dma::(0, buf) } } - fn data_in<'a>(&'a mut self, buf: &'a [u8], _first: bool, last: bool) -> Self::DataInFuture<'a> { - async move { + async fn data_in(&mut self, buf: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { + let regs = T::regs(); + regs.events_ep0datadone.reset(); + + regs.shorts.write(|w| w.ep0datadone_ep0status().bit(last)); + + // This starts a TX on EP0. events_ep0datadone notifies when done. + unsafe { write_dma::(0, buf) } + + regs.intenset.write(|w| { + w.usbreset().set(); + w.ep0setup().set(); + w.ep0datadone().set() + }); + + poll_fn(|cx| { + cx.waker().wake_by_ref(); + EP0_WAKER.register(cx.waker()); let regs = T::regs(); - regs.events_ep0datadone.reset(); - - regs.shorts.write(|w| w.ep0datadone_ep0status().bit(last)); - - // This starts a TX on EP0. events_ep0datadone notifies when done. - unsafe { write_dma::(0, buf) } - - regs.intenset.write(|w| { - w.usbreset().set(); - w.ep0setup().set(); - w.ep0datadone().set() - }); - - poll_fn(|cx| { - cx.waker().wake_by_ref(); - EP0_WAKER.register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(Ok(())) - } else if regs.events_usbreset.read().bits() != 0 { - trace!("aborted control data_in: usb reset"); - Poll::Ready(Err(EndpointError::Disabled)) - } else if regs.events_ep0setup.read().bits() != 0 { - trace!("aborted control data_in: received another SETUP"); - Poll::Ready(Err(EndpointError::Disabled)) - } else { - Poll::Pending - } - }) - .await - } + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset.read().bits() != 0 { + trace!("aborted control data_in: usb reset"); + Poll::Ready(Err(EndpointError::Disabled)) + } else if regs.events_ep0setup.read().bits() != 0 { + trace!("aborted control data_in: received another SETUP"); + Poll::Ready(Err(EndpointError::Disabled)) + } else { + Poll::Pending + } + }) + .await } - fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a> { - async move { - let regs = T::regs(); - regs.tasks_ep0status.write(|w| w.tasks_ep0status().bit(true)); - } + async fn accept(&mut self) { + let regs = T::regs(); + regs.tasks_ep0status.write(|w| w.tasks_ep0status().bit(true)); } - fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a> { - async move { - let regs = T::regs(); - regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); - } + async fn reject(&mut self) { + let regs = T::regs(); + regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); + } + + async fn accept_set_address(&mut self, _addr: u8) { + self.accept().await; + // Nothing to do, the peripheral handles this. } } @@ -944,7 +941,9 @@ pub(crate) mod sealed { } } +/// USB peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/wdt.rs b/embassy-nrf/src/wdt.rs index 8760aa301..40a674424 100644 --- a/embassy-nrf/src/wdt.rs +++ b/embassy-nrf/src/wdt.rs @@ -1,4 +1,4 @@ -//! HAL interface to the WDT peripheral. +//! Watchdog Timer (WDT) driver. //! //! This HAL implements a basic watchdog timer with 1..=8 handles. //! Once the watchdog has been started, it cannot be stopped. @@ -8,6 +8,7 @@ use crate::peripherals; const MIN_TICKS: u32 = 15; +/// WDT configuration. #[non_exhaustive] pub struct Config { /// Number of 32768 Hz ticks in each watchdog period. @@ -23,6 +24,30 @@ pub struct Config { pub run_during_debug_halt: bool, } +impl Config { + /// Create a config structure from the current configuration of the WDT + /// peripheral. + pub fn try_new(_wdt: &peripherals::WDT) -> Option { + let r = unsafe { &*WDT::ptr() }; + + #[cfg(not(feature = "_nrf9160"))] + let runstatus = r.runstatus.read().runstatus().bit(); + #[cfg(feature = "_nrf9160")] + let runstatus = r.runstatus.read().runstatuswdt().bit(); + + if runstatus { + let config = r.config.read(); + Some(Self { + timeout_ticks: r.crv.read().bits(), + run_during_sleep: config.sleep().bit(), + run_during_debug_halt: config.halt().bit(), + }) + } else { + None + } + } +} + impl Default for Config { fn default() -> Self { Self { @@ -33,13 +58,13 @@ impl Default for Config { } } -/// An interface to the Watchdog. +/// Watchdog driver. pub struct Watchdog { _private: (), } impl Watchdog { - /// Try to create a new watchdog instance from the peripheral. + /// Try to create a new watchdog driver. /// /// This function will return an error if the watchdog is already active /// with a `config` different to the requested one, or a different number of @@ -131,6 +156,7 @@ impl Watchdog { } } +/// Watchdog handle. pub struct WatchdogHandle { index: u8, } diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index c56858415..209c665b0 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -13,7 +13,10 @@ flavors = [ ] [features] -defmt = ["dep:defmt", "embassy-usb-driver?/defmt"] +defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"] + +# critical section that is safe for multicore use +critical-section-impl = ["critical-section/restore-state-u8"] # Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. # This is unstable because semver-minor (non-breaking) releases of embassy-rp may major-bump (breaking) the PAC version. @@ -26,6 +29,7 @@ time-driver = [] rom-func-cache = [] intrinsics = [] rom-v2-intrinsics = [] +pio = ["dep:pio", "dep:pio-proc"] # Enable nightly-only features nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly", "dep:embassy-usb-driver", "dep:embedded-io"] @@ -53,12 +57,18 @@ cortex-m = "0.7.6" critical-section = "1.1" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } chrono = { version = "0.4", default-features = false, optional = true } -embedded-io = { version = "0.3.0", features = ["async"], optional = true } +embedded-io = { version = "0.4.0", features = ["async"], optional = true } +embedded-storage = { version = "0.3" } +rand_core = "0.6.4" rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] } #rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9", optional = true} -embedded-hal-async = { version = "=0.1.0-alpha.2", optional = true} +embedded-hal-async = { version = "=0.2.0-alpha.0", optional = true} embedded-hal-nb = { version = "=1.0.0-alpha.1", optional = true} + +paste = "1.0" +pio-proc = {version= "0.2", optional = true} +pio = {version= "0.2.1", optional = true} diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs new file mode 100644 index 000000000..025c6f917 --- /dev/null +++ b/embassy-rp/src/adc.rs @@ -0,0 +1,173 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::into_ref; +use embassy_sync::waitqueue::AtomicWaker; +use embedded_hal_02::adc::{Channel, OneShot}; + +use crate::interrupt::{self, InterruptExt}; +use crate::peripherals::ADC; +use crate::{pac, peripherals, Peripheral}; +static WAKER: AtomicWaker = AtomicWaker::new(); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +#[non_exhaustive] +pub struct Config {} + +impl Default for Config { + fn default() -> Self { + Self {} + } +} +pub struct Adc<'d> { + phantom: PhantomData<&'d ADC>, +} + +impl<'d> Adc<'d> { + #[inline] + fn regs() -> pac::adc::Adc { + pac::ADC + } + + #[inline] + fn reset() -> pac::resets::regs::Peripherals { + let mut ret = pac::resets::regs::Peripherals::default(); + ret.set_adc(true); + ret + } + + pub fn new( + _inner: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + _config: Config, + ) -> Self { + into_ref!(irq); + unsafe { + let reset = Self::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + let r = Self::regs(); + // Enable ADC + r.cs().write(|w| w.set_en(true)); + // Wait for ADC ready + while !r.cs().read().ready() {} + } + + // Setup IRQ + irq.disable(); + irq.set_handler(|_| unsafe { + let r = Self::regs(); + r.inte().write(|w| w.set_fifo(false)); + WAKER.wake(); + }); + irq.unpend(); + irq.enable(); + + Self { phantom: PhantomData } + } + + async fn wait_for_ready() { + let r = Self::regs(); + unsafe { + r.inte().write(|w| w.set_fifo(true)); + compiler_fence(Ordering::SeqCst); + poll_fn(|cx| { + WAKER.register(cx.waker()); + if r.cs().read().ready() { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + } + } + + pub async fn read, ID = u8>>(&mut self, _pin: &mut PIN) -> u16 { + let r = Self::regs(); + unsafe { + r.cs().modify(|w| { + w.set_ainsel(PIN::channel()); + w.set_start_once(true) + }); + Self::wait_for_ready().await; + r.result().read().result().into() + } + } + + pub async fn read_temperature(&mut self) -> u16 { + let r = Self::regs(); + unsafe { + r.cs().modify(|w| w.set_ts_en(true)); + if !r.cs().read().ready() { + Self::wait_for_ready().await; + } + r.cs().modify(|w| { + w.set_ainsel(4); + w.set_start_once(true) + }); + Self::wait_for_ready().await; + r.result().read().result().into() + } + } + + pub fn blocking_read, ID = u8>>(&mut self, _pin: &mut PIN) -> u16 { + let r = Self::regs(); + unsafe { + r.cs().modify(|w| { + w.set_ainsel(PIN::channel()); + w.set_start_once(true) + }); + while !r.cs().read().ready() {} + r.result().read().result().into() + } + } + + pub fn blocking_read_temperature(&mut self) -> u16 { + let r = Self::regs(); + unsafe { + r.cs().modify(|w| w.set_ts_en(true)); + while !r.cs().read().ready() {} + r.cs().modify(|w| { + w.set_ainsel(4); + w.set_start_once(true) + }); + while !r.cs().read().ready() {} + r.result().read().result().into() + } + } +} + +macro_rules! impl_pin { + ($pin:ident, $channel:expr) => { + impl Channel> for peripherals::$pin { + type ID = u8; + fn channel() -> u8 { + $channel + } + } + }; +} + +impl_pin!(PIN_26, 0); +impl_pin!(PIN_27, 1); +impl_pin!(PIN_28, 2); +impl_pin!(PIN_29, 3); + +impl OneShot, WORD, PIN> for Adc<'static> +where + WORD: From, + PIN: Channel, ID = u8>, +{ + type Error = (); + fn read(&mut self, pin: &mut PIN) -> nb::Result { + Ok(self.blocking_read(pin).into()) + } +} diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 1c446f389..85c9bbb7a 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -5,7 +5,7 @@ use crate::{pac, reset}; const XOSC_MHZ: u32 = 12; /// safety: must be called exactly once at bootup -pub unsafe fn init() { +pub(crate) unsafe fn init() { // Reset everything except: // - QSPI (we're using it to run this code!) // - PLLs (it may be suicide if that's what's clocking us) @@ -196,3 +196,40 @@ unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1: // Turn on post divider p.pwr().modify(|w| w.set_postdivpd(false)); } + +/// Random number generator based on the ROSC RANDOMBIT register. +/// +/// This will not produce random values if the ROSC is stopped or run at some +/// harmonic of the bus frequency. With default clock settings these are not +/// issues. +pub struct RoscRng; + +impl RoscRng { + fn next_u8() -> u8 { + let random_reg = pac::ROSC.randombit(); + let mut acc = 0; + for _ in 0..u8::BITS { + acc <<= 1; + acc |= unsafe { random_reg.read().randombit() as u8 }; + } + acc + } +} + +impl rand_core::RngCore for RoscRng { + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + Ok(self.fill_bytes(dest)) + } + + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.fill_with(Self::next_u8) + } +} diff --git a/embassy-rp/src/critical_section_impl.rs b/embassy-rp/src/critical_section_impl.rs new file mode 100644 index 000000000..ce284c856 --- /dev/null +++ b/embassy-rp/src/critical_section_impl.rs @@ -0,0 +1,142 @@ +use core::sync::atomic::{AtomicU8, Ordering}; + +use crate::pac; + +struct RpSpinlockCs; +critical_section::set_impl!(RpSpinlockCs); + +/// Marker value to indicate no-one has the lock. +/// +/// Initialising `LOCK_OWNER` to 0 means cheaper static initialisation so it's the best choice +const LOCK_UNOWNED: u8 = 0; + +/// Indicates which core owns the lock so that we can call critical_section recursively. +/// +/// 0 = no one has the lock, 1 = core0 has the lock, 2 = core1 has the lock +static LOCK_OWNER: AtomicU8 = AtomicU8::new(LOCK_UNOWNED); + +/// Marker value to indicate that we already owned the lock when we started the `critical_section`. +/// +/// Since we can't take the spinlock when we already have it, we need some other way to keep track of `critical_section` ownership. +/// `critical_section` provides a token for communicating between `acquire` and `release` so we use that. +/// If we're the outermost call to `critical_section` we use the values 0 and 1 to indicate we should release the spinlock and set the interrupts back to disabled and enabled, respectively. +/// The value 2 indicates that we aren't the outermost call, and should not release the spinlock or re-enable interrupts in `release` +const LOCK_ALREADY_OWNED: u8 = 2; + +unsafe impl critical_section::Impl for RpSpinlockCs { + unsafe fn acquire() -> u8 { + RpSpinlockCs::acquire() + } + + unsafe fn release(token: u8) { + RpSpinlockCs::release(token); + } +} + +impl RpSpinlockCs { + unsafe fn acquire() -> u8 { + // Store the initial interrupt state and current core id in stack variables + let interrupts_active = cortex_m::register::primask::read().is_active(); + // We reserved 0 as our `LOCK_UNOWNED` value, so add 1 to core_id so we get 1 for core0, 2 for core1. + let core = pac::SIO.cpuid().read() as u8 + 1; + // Do we already own the spinlock? + if LOCK_OWNER.load(Ordering::Acquire) == core { + // We already own the lock, so we must have called acquire within a critical_section. + // Return the magic inner-loop value so that we know not to re-enable interrupts in release() + LOCK_ALREADY_OWNED + } else { + // Spin until we get the lock + loop { + // Need to disable interrupts to ensure that we will not deadlock + // if an interrupt enters critical_section::Impl after we acquire the lock + cortex_m::interrupt::disable(); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Read the spinlock reserved for `critical_section` + if let Some(lock) = Spinlock31::try_claim() { + // We just acquired the lock. + // 1. Forget it, so we don't immediately unlock + core::mem::forget(lock); + // 2. Store which core we are so we can tell if we're called recursively + LOCK_OWNER.store(core, Ordering::Relaxed); + break; + } + // We didn't get the lock, enable interrupts if they were enabled before we started + if interrupts_active { + cortex_m::interrupt::enable(); + } + } + // If we broke out of the loop we have just acquired the lock + // As the outermost loop, we want to return the interrupt status to restore later + interrupts_active as _ + } + } + + unsafe fn release(token: u8) { + // Did we already own the lock at the start of the `critical_section`? + if token != LOCK_ALREADY_OWNED { + // No, it wasn't owned at the start of this `critical_section`, so this core no longer owns it. + // Set `LOCK_OWNER` back to `LOCK_UNOWNED` to ensure the next critical section tries to obtain the spinlock instead + LOCK_OWNER.store(LOCK_UNOWNED, Ordering::Relaxed); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Release the spinlock to allow others to enter critical_section again + Spinlock31::release(); + // Re-enable interrupts if they were enabled when we first called acquire() + // We only do this on the outermost `critical_section` to ensure interrupts stay disabled + // for the whole time that we have the lock + if token != 0 { + cortex_m::interrupt::enable(); + } + } + } +} + +pub struct Spinlock(core::marker::PhantomData<()>) +where + Spinlock: SpinlockValid; + +impl Spinlock +where + Spinlock: SpinlockValid, +{ + /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is + /// already in use somewhere else. + pub fn try_claim() -> Option { + // Safety: We're only reading from this register + unsafe { + let lock = pac::SIO.spinlock(N).read(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + unsafe { + // Write (any value): release the lock + pac::SIO.spinlock(N).write_value(1); + } + } +} + +impl Drop for Spinlock +where + Spinlock: SpinlockValid, +{ + fn drop(&mut self) { + // This is safe because we own the object, and hence hold the lock. + unsafe { Self::release() } + } +} + +pub(crate) type Spinlock31 = Spinlock<31>; +pub trait SpinlockValid {} +impl SpinlockValid for Spinlock<31> {} diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index 410c48666..fd281fd5d 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs @@ -191,7 +191,7 @@ impl<'a, C: Channel> Future for Transfer<'a, C> { } } -const CHANNEL_COUNT: usize = 12; +pub(crate) const CHANNEL_COUNT: usize = 12; const NEW_AW: AtomicWaker = AtomicWaker::new(); static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs new file mode 100644 index 000000000..f2137ebe1 --- /dev/null +++ b/embassy-rp/src/flash.rs @@ -0,0 +1,481 @@ +use core::marker::PhantomData; + +use embassy_hal_common::Peripheral; +use embedded_storage::nor_flash::{ + check_erase, check_read, check_write, ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, + ReadNorFlash, +}; + +use crate::pac; +use crate::peripherals::FLASH; + +pub const FLASH_BASE: usize = 0x10000000; + +// **NOTE**: +// +// These limitations are currently enforced because of using the +// RP2040 boot-rom flash functions, that are optimized for flash compatibility +// rather than performance. +pub const PAGE_SIZE: usize = 256; +pub const WRITE_SIZE: usize = 1; +pub const READ_SIZE: usize = 1; +pub const ERASE_SIZE: usize = 4096; + +/// Error type for NVMC operations. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Opration using a location not in flash. + OutOfBounds, + /// Unaligned operation or using unaligned buffers. + Unaligned, + InvalidCore, + Other, +} + +impl From for Error { + fn from(e: NorFlashErrorKind) -> Self { + match e { + NorFlashErrorKind::NotAligned => Self::Unaligned, + NorFlashErrorKind::OutOfBounds => Self::OutOfBounds, + _ => Self::Other, + } + } +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Self::Unaligned => NorFlashErrorKind::NotAligned, + _ => NorFlashErrorKind::Other, + } + } +} + +pub struct Flash<'d, T: Instance, const FLASH_SIZE: usize>(PhantomData<&'d mut T>); + +impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> { + pub fn new(_flash: impl Peripheral

+ 'd) -> Self { + Self(PhantomData) + } + + pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + trace!( + "Reading from 0x{:x} to 0x{:x}", + FLASH_BASE + offset as usize, + FLASH_BASE + offset as usize + bytes.len() + ); + check_read(self, offset, bytes.len())?; + + let flash_data = unsafe { core::slice::from_raw_parts((FLASH_BASE as u32 + offset) as *const u8, bytes.len()) }; + + bytes.copy_from_slice(flash_data); + Ok(()) + } + + pub fn capacity(&self) -> usize { + FLASH_SIZE + } + + pub fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + check_erase(self, from, to)?; + + trace!( + "Erasing from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + from, + FLASH_BASE as u32 + to + ); + + let len = to - from; + + unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len, true))? }; + + Ok(()) + } + + pub fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + check_write(self, offset, bytes.len())?; + + trace!("Writing {:?} bytes to 0x{:x}", bytes.len(), FLASH_BASE as u32 + offset); + + let end_offset = offset as usize + bytes.len(); + + let padded_offset = (offset as *const u8).align_offset(PAGE_SIZE); + let start_padding = core::cmp::min(padded_offset, bytes.len()); + + // Pad in the beginning + if start_padding > 0 { + let start = PAGE_SIZE - padded_offset; + let end = start + start_padding; + + let mut pad_buf = [0xFF_u8; PAGE_SIZE]; + pad_buf[start..end].copy_from_slice(&bytes[..start_padding]); + + let unaligned_offset = offset as usize - start; + + unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true))? } + } + + let remaining_len = bytes.len() - start_padding; + let end_padding = start_padding + PAGE_SIZE * (remaining_len / PAGE_SIZE); + + // Write aligned slice of length in multiples of 256 bytes + // If the remaining bytes to be written is more than a full page. + if remaining_len >= PAGE_SIZE { + let mut aligned_offset = if start_padding > 0 { + offset as usize + padded_offset + } else { + offset as usize + }; + + if bytes.as_ptr() as usize >= 0x2000_0000 { + let aligned_data = &bytes[start_padding..end_padding]; + + unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data, true))? } + } else { + for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { + let mut ram_buf = [0xFF_u8; PAGE_SIZE]; + ram_buf.copy_from_slice(chunk); + unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf, true))? } + aligned_offset += PAGE_SIZE; + } + } + } + + // Pad in the end + let rem_offset = (end_offset as *const u8).align_offset(PAGE_SIZE); + let rem_padding = remaining_len % PAGE_SIZE; + if rem_padding > 0 { + let mut pad_buf = [0xFF_u8; PAGE_SIZE]; + pad_buf[..rem_padding].copy_from_slice(&bytes[end_padding..]); + + let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); + + unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true))? } + } + + Ok(()) + } + + /// Make sure to uphold the contract points with rp2040-flash. + /// - interrupts must be disabled + /// - DMA must not access flash memory + unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> { + let dma_status = &mut [false; crate::dma::CHANNEL_COUNT]; + + // Make sure we're running on CORE0 + let core_id: u32 = unsafe { pac::SIO.cpuid().read() }; + if core_id != 0 { + return Err(Error::InvalidCore); + } + + // Make sure CORE1 is paused during the entire duration of the RAM function + crate::multicore::pause_core1(); + + critical_section::with(|_| { + // Pause all DMA channels for the duration of the ram operation + for (number, status) in dma_status.iter_mut().enumerate() { + let ch = crate::pac::DMA.ch(number as _); + *status = ch.ctrl_trig().read().en(); + if *status { + ch.ctrl_trig().modify(|w| w.set_en(false)); + } + } + + // Run our flash operation in RAM + operation(); + + // Re-enable previously enabled DMA channels + for (number, status) in dma_status.iter().enumerate() { + let ch = crate::pac::DMA.ch(number as _); + if *status { + ch.ctrl_trig().modify(|w| w.set_en(true)); + } + } + }); + + // Resume CORE1 execution + crate::multicore::resume_core1(); + Ok(()) + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, FLASH_SIZE> { + type Error = Error; +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, FLASH_SIZE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.capacity() + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, FLASH_SIZE> {} + +impl<'d, T: Instance, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, FLASH_SIZE> { + const WRITE_SIZE: usize = WRITE_SIZE; + + const ERASE_SIZE: usize = ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } +} + +#[allow(dead_code)] +mod ram_helpers { + use core::marker::PhantomData; + + use crate::rom_data; + + #[repr(C)] + struct FlashFunctionPointers<'a> { + connect_internal_flash: unsafe extern "C" fn() -> (), + flash_exit_xip: unsafe extern "C" fn() -> (), + flash_range_erase: Option ()>, + flash_range_program: Option ()>, + flash_flush_cache: unsafe extern "C" fn() -> (), + flash_enter_cmd_xip: unsafe extern "C" fn() -> (), + phantom: PhantomData<&'a ()>, + } + + #[allow(unused)] + fn flash_function_pointers(erase: bool, write: bool) -> FlashFunctionPointers<'static> { + FlashFunctionPointers { + connect_internal_flash: rom_data::connect_internal_flash::ptr(), + flash_exit_xip: rom_data::flash_exit_xip::ptr(), + flash_range_erase: if erase { + Some(rom_data::flash_range_erase::ptr()) + } else { + None + }, + flash_range_program: if write { + Some(rom_data::flash_range_program::ptr()) + } else { + None + }, + flash_flush_cache: rom_data::flash_flush_cache::ptr(), + flash_enter_cmd_xip: rom_data::flash_enter_cmd_xip::ptr(), + phantom: PhantomData, + } + } + + #[allow(unused)] + /// # Safety + /// + /// `boot2` must contain a valid 2nd stage boot loader which can be called to re-initialize XIP mode + unsafe fn flash_function_pointers_with_boot2(erase: bool, write: bool, boot2: &[u32; 64]) -> FlashFunctionPointers { + let boot2_fn_ptr = (boot2 as *const u32 as *const u8).offset(1); + let boot2_fn: unsafe extern "C" fn() -> () = core::mem::transmute(boot2_fn_ptr); + FlashFunctionPointers { + connect_internal_flash: rom_data::connect_internal_flash::ptr(), + flash_exit_xip: rom_data::flash_exit_xip::ptr(), + flash_range_erase: if erase { + Some(rom_data::flash_range_erase::ptr()) + } else { + None + }, + flash_range_program: if write { + Some(rom_data::flash_range_program::ptr()) + } else { + None + }, + flash_flush_cache: rom_data::flash_flush_cache::ptr(), + flash_enter_cmd_xip: boot2_fn, + phantom: PhantomData, + } + } + + /// Erase a flash range starting at `addr` with length `len`. + /// + /// `addr` and `len` must be multiples of 4096 + /// + /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # 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 + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_erase(addr: u32, len: u32, use_boot2: bool) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if use_boot2 { + rom_data::memcpy44(&mut boot2 as *mut _, super::FLASH_BASE as *const _, 256); + flash_function_pointers_with_boot2(true, false, &boot2) + } else { + flash_function_pointers(true, false) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner(addr, len, None, &ptrs as *const FlashFunctionPointers); + } + + /// Erase and rewrite a flash range starting at `addr` with data `data`. + /// + /// `addr` and `data.len()` must be multiples of 4096 + /// + /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # 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 + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8], use_boot2: bool) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if use_boot2 { + rom_data::memcpy44(&mut boot2 as *mut _, super::FLASH_BASE as *const _, 256); + flash_function_pointers_with_boot2(true, true, &boot2) + } else { + flash_function_pointers(true, true) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner( + addr, + data.len() as u32, + Some(data), + &ptrs as *const FlashFunctionPointers, + ); + } + + /// Write a flash range starting at `addr` with data `data`. + /// + /// `addr` and `data.len()` must be multiples of 256 + /// + /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # 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 + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_program(addr: u32, data: &[u8], use_boot2: bool) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if use_boot2 { + rom_data::memcpy44(&mut boot2 as *mut _, super::FLASH_BASE as *const _, 256); + flash_function_pointers_with_boot2(false, true, &boot2) + } else { + flash_function_pointers(false, true) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner( + addr, + data.len() as u32, + Some(data), + &ptrs as *const FlashFunctionPointers, + ); + } + + /// # 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"] + 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", + "mov r9, r2", + "mov r10, r1", + "ldr r4, [{ptrs}, #0]", + "blx r4", // connect_internal_flash() + + "ldr r4, [{ptrs}, #4]", + "blx r4", // flash_exit_xip() + + "mov r0, r8", // r0 = addr + "mov r1, r10", // r1 = len + "movs r2, #1", + "lsls r2, r2, #31", // r2 = 1 << 31 + "movs r3, #0", // r3 = 0 + "ldr r4, [{ptrs}, #8]", + "cmp r4, #0", + "beq 1f", + "blx r4", // flash_range_erase(addr, len, 1 << 31, 0) + "1:", + + "mov r0, r8", // r0 = addr + "mov r1, r9", // r0 = data + "mov r2, r10", // r2 = len + "ldr r4, [{ptrs}, #12]", + "cmp r4, #0", + "beq 1f", + "blx r4", // flash_range_program(addr, data, len); + "1:", + + "ldr r4, [{ptrs}, #16]", + "blx r4", // flash_flush_cache(); + + "ldr r4, [{ptrs}, #20]", + "blx r4", // flash_enter_cmd_xip(); + ptrs = in(reg) ptrs, + // Registers r8-r15 are not allocated automatically, + // so assign them manually. We need to use them as + // otherwise there are not enough registers available. + in("r0") addr, + in("r2") data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()), + in("r1") len, + out("r3") _, + out("r4") _, + lateout("r8") _, + lateout("r9") _, + lateout("r10") _, + clobber_abi("C"), + ); + } +} + +mod sealed { + pub trait Instance {} +} + +pub trait Instance: sealed::Instance {} + +impl sealed::Instance for FLASH {} +impl Instance for FLASH {} diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index f79f592b4..4abb98394 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -48,6 +48,21 @@ pub enum Pull { Down, } +/// Drive strength of an output +#[derive(Debug, Eq, PartialEq)] +pub enum Drive { + _2mA, + _4mA, + _8mA, + _12mA, +} +/// Slew rate of an output +#[derive(Debug, Eq, PartialEq)] +pub enum SlewRate { + Fast, + Slow, +} + /// A GPIO bank with up to 32 pins. #[derive(Debug, Eq, PartialEq)] pub enum Bank { @@ -411,6 +426,16 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> { pub fn toggle(&mut self) { self.pin.toggle_set_as_output() } + + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } } /// GPIO flexible pin. @@ -449,13 +474,40 @@ impl<'d, T: Pin> Flex<'d, T> { #[inline] pub fn set_pull(&mut self, pull: Pull) { unsafe { - self.pin.pad_ctrl().write(|w| { + self.pin.pad_ctrl().modify(|w| { w.set_ie(true); - match pull { - Pull::Up => w.set_pue(true), - Pull::Down => w.set_pde(true), - Pull::None => {} - } + let (pu, pd) = match pull { + Pull::Up => (true, false), + Pull::Down => (false, true), + Pull::None => (false, false), + }; + w.set_pue(pu); + w.set_pde(pd); + }); + } + } + + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + unsafe { + self.pin.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2MA, + Drive::_4mA => pac::pads::vals::Drive::_4MA, + Drive::_8mA => pac::pads::vals::Drive::_8MA, + Drive::_12mA => pac::pads::vals::Drive::_12MA, + }); + }); + } + } + + // Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + unsafe { + self.pin.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); }); } } @@ -691,6 +743,7 @@ macro_rules! impl_pin { ($name:ident, $bank:expr, $pin_num:expr) => { impl Pin for peripherals::$name {} impl sealed::Pin for peripherals::$name { + #[inline] fn pin_bank(&self) -> u8 { ($bank as u8) * 32 + $pin_num } @@ -791,6 +844,18 @@ mod eh02 { } } + impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for OutputOpenDrain<'d, T> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d, T> { type Error = Infallible; @@ -870,9 +935,6 @@ mod eh02 { mod eh1 { use core::convert::Infallible; - #[cfg(feature = "nightly")] - use futures::FutureExt; - use super::*; impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Input<'d, T> { @@ -949,6 +1011,16 @@ mod eh1 { } } + impl<'d, T: Pin> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d, T> { + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Flex<'d, T> { type Error = Infallible; } @@ -991,57 +1063,57 @@ mod eh1 { #[cfg(feature = "nightly")] impl<'d, T: Pin> embedded_hal_async::digital::Wait for Flex<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) } } #[cfg(feature = "nightly")] impl<'d, T: Pin> embedded_hal_async::digital::Wait for Input<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) } } } diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs index 52f910cef..e48e16d81 100644 --- a/embassy-rp/src/i2c.rs +++ b/embassy-rp/src/i2c.rs @@ -1,9 +1,12 @@ +use core::future; use core::marker::PhantomData; +use core::task::Poll; +use embassy_cortex_m::interrupt::InterruptExt; use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; use pac::i2c; -use crate::dma::AnyChannel; use crate::gpio::sealed::Pin; use crate::gpio::AnyPin; use crate::{pac, peripherals, Peripheral}; @@ -52,31 +55,276 @@ impl Default for Config { const FIFO_SIZE: u8 = 16; pub struct I2c<'d, T: Instance, M: Mode> { - _tx_dma: Option>, - _rx_dma: Option>, - _dma_buf: [u16; 256], phantom: PhantomData<(&'d mut T, M)>, } impl<'d, T: Instance> I2c<'d, T, Blocking> { pub fn new_blocking( - _peri: impl Peripheral

+ 'd, + peri: impl Peripheral

+ 'd, scl: impl Peripheral

> + 'd, sda: impl Peripheral

> + 'd, config: Config, ) -> Self { into_ref!(scl, sda); - Self::new_inner(_peri, scl.map_into(), sda.map_into(), None, None, config) + Self::new_inner(peri, scl.map_into(), sda.map_into(), config) } } -impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { +impl<'d, T: Instance> I2c<'d, T, Async> { + pub fn new_async( + peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(scl, sda, irq); + + let i2c = Self::new_inner(peri, scl.map_into(), sda.map_into(), config); + + irq.set_handler(Self::on_interrupt); + unsafe { + let i2c = T::regs(); + + // mask everything initially + i2c.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0)); + } + irq.unpend(); + debug_assert!(!irq.is_pending()); + irq.enable(); + + i2c + } + + /// Calls `f` to check if we are ready or not. + /// If not, `g` is called once the waker is set (to eg enable the required interrupts). + async fn wait_on(&mut self, mut f: F, mut g: G) -> U + where + F: FnMut(&mut Self) -> Poll, + G: FnMut(&mut Self), + { + future::poll_fn(|cx| { + let r = f(self); + + if r.is_pending() { + T::waker().register(cx.waker()); + g(self); + } + r + }) + .await + } + + // Mask interrupts and wake any task waiting for this interrupt + unsafe fn on_interrupt(_: *mut ()) { + let i2c = T::regs(); + i2c.ic_intr_mask().write_value(pac::i2c::regs::IcIntrMask::default()); + + T::waker().wake(); + } + + async fn read_async_internal(&mut self, buffer: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> { + if buffer.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + let p = T::regs(); + + let mut remaining = buffer.len(); + let mut remaining_queue = buffer.len(); + + let mut abort_reason = Ok(()); + + while remaining > 0 { + // Waggle SCK - basically the same as write + let tx_fifo_space = Self::tx_fifo_capacity(); + let mut batch = 0; + + debug_assert!(remaining_queue > 0); + + for _ in 0..remaining_queue.min(tx_fifo_space as usize) { + remaining_queue -= 1; + let last = remaining_queue == 0; + batch += 1; + + unsafe { + p.ic_data_cmd().write(|w| { + w.set_restart(restart && remaining_queue == buffer.len() - 1); + w.set_stop(last && send_stop); + w.set_cmd(true); + }); + } + } + + // We've either run out of txfifo or just plain finished setting up + // the clocks for the message - either way we need to wait for rx + // data. + + debug_assert!(batch > 0); + let res = self + .wait_on( + |me| { + let rxfifo = Self::rx_fifo_len(); + if let Err(abort_reason) = me.read_and_clear_abort_reason() { + Poll::Ready(Err(abort_reason)) + } else if rxfifo >= batch { + Poll::Ready(Ok(rxfifo)) + } else { + Poll::Pending + } + }, + |_me| unsafe { + // Set the read threshold to the number of bytes we're + // expecting so we don't get spurious interrupts. + p.ic_rx_tl().write(|w| w.set_rx_tl(batch - 1)); + + p.ic_intr_mask().modify(|w| { + w.set_m_rx_full(true); + w.set_m_tx_abrt(true); + }); + }, + ) + .await; + + match res { + Err(reason) => { + abort_reason = Err(reason); + break; + } + Ok(rxfifo) => { + // Fetch things from rx fifo. We're assuming we're the only + // rxfifo reader, so nothing else can take things from it. + let rxbytes = (rxfifo as usize).min(remaining); + let received = buffer.len() - remaining; + for b in &mut buffer[received..received + rxbytes] { + *b = unsafe { p.ic_data_cmd().read().dat() }; + } + remaining -= rxbytes; + } + }; + } + + self.wait_stop_det(abort_reason, send_stop).await + } + + async fn write_async_internal( + &mut self, + bytes: impl IntoIterator, + send_stop: bool, + ) -> Result<(), Error> { + let p = T::regs(); + + let mut bytes = bytes.into_iter().peekable(); + + let res = 'xmit: loop { + let tx_fifo_space = Self::tx_fifo_capacity(); + + for _ in 0..tx_fifo_space { + if let Some(byte) = bytes.next() { + let last = bytes.peek().is_none(); + + unsafe { + p.ic_data_cmd().write(|w| { + w.set_stop(last && send_stop); + w.set_cmd(false); + w.set_dat(byte); + }); + } + } else { + break 'xmit Ok(()); + } + } + + let res = self + .wait_on( + |me| { + if let abort_reason @ Err(_) = me.read_and_clear_abort_reason() { + Poll::Ready(abort_reason) + } else if !Self::tx_fifo_full() { + // resume if there's any space free in the tx fifo + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }, + |_me| unsafe { + // Set tx "free" threshold a little high so that we get + // woken before the fifo completely drains to minimize + // transfer stalls. + p.ic_tx_tl().write(|w| w.set_tx_tl(1)); + + p.ic_intr_mask().modify(|w| { + w.set_m_tx_empty(true); + w.set_m_tx_abrt(true); + }) + }, + ) + .await; + if res.is_err() { + break res; + } + }; + + self.wait_stop_det(res, send_stop).await + } + + /// Helper to wait for a stop bit, for both tx and rx. If we had an abort, + /// then we'll get a hardware-generated stop, otherwise wait for a stop if + /// we're expecting it. + /// + /// Also handles an abort which arises while processing the tx fifo. + async fn wait_stop_det(&mut self, had_abort: Result<(), Error>, do_stop: bool) -> Result<(), Error> { + if had_abort.is_err() || do_stop { + let p = T::regs(); + + let had_abort2 = self + .wait_on( + |me| unsafe { + // We could see an abort while processing fifo backlog, + // so handle it here. + let abort = me.read_and_clear_abort_reason(); + if had_abort.is_ok() && abort.is_err() { + Poll::Ready(abort) + } else if p.ic_raw_intr_stat().read().stop_det() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }, + |_me| unsafe { + p.ic_intr_mask().modify(|w| { + w.set_m_stop_det(true); + w.set_m_tx_abrt(true); + }); + }, + ) + .await; + unsafe { + p.ic_clr_stop_det().read(); + } + + had_abort.and(had_abort2) + } else { + had_abort + } + } + + pub async fn read_async(&mut self, addr: u16, buffer: &mut [u8]) -> Result<(), Error> { + Self::setup(addr)?; + self.read_async_internal(buffer, false, true).await + } + + pub async fn write_async(&mut self, addr: u16, bytes: impl IntoIterator) -> Result<(), Error> { + Self::setup(addr)?; + self.write_async_internal(bytes, true).await + } +} + +impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { fn new_inner( _peri: impl Peripheral

+ 'd, scl: PeripheralRef<'d, AnyPin>, sda: PeripheralRef<'d, AnyPin>, - _tx_dma: Option>, - _rx_dma: Option>, config: Config, ) -> Self { into_ref!(_peri); @@ -87,6 +335,10 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { let p = T::regs(); unsafe { + let reset = T::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + p.ic_enable().write(|w| w.set_enable(false)); // Select controller mode & speed @@ -172,12 +424,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { p.ic_enable().write(|w| w.set_enable(true)); } - Self { - _tx_dma, - _rx_dma, - _dma_buf: [0; 256], - phantom: PhantomData, - } + Self { phantom: PhantomData } } fn setup(addr: u16) -> Result<(), Error> { @@ -198,6 +445,23 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { Ok(()) } + #[inline] + fn tx_fifo_full() -> bool { + Self::tx_fifo_capacity() == 0 + } + + #[inline] + fn tx_fifo_capacity() -> u8 { + let p = T::regs(); + unsafe { FIFO_SIZE - p.ic_txflr().read().txflr() } + } + + #[inline] + fn rx_fifo_len() -> u8 { + let p = T::regs(); + unsafe { p.ic_rxflr().read().rxflr() } + } + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { let p = T::regs(); unsafe { @@ -240,7 +504,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { // NOTE(unsafe) We have &mut self unsafe { // wait until there is space in the FIFO to write the next byte - while p.ic_txflr().read().txflr() == FIFO_SIZE {} + while Self::tx_fifo_full() {} p.ic_data_cmd().write(|w| { w.set_restart(restart && first); @@ -249,7 +513,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { w.set_cmd(true); }); - while p.ic_rxflr().read().rxflr() == 0 { + while Self::rx_fifo_len() == 0 { self.read_and_clear_abort_reason()?; } @@ -451,6 +715,70 @@ mod eh1 { } } } +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod nightly { + use embedded_hal_1::i2c::Operation; + use embedded_hal_async::i2c::AddressMode; + + use super::*; + + impl<'d, A, T> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> + where + A: AddressMode + Into + 'static, + T: Instance + 'd, + { + async fn read<'a>(&'a mut self, address: A, read: &'a mut [u8]) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + Self::setup(addr)?; + self.read_async_internal(read, false, true).await + } + + async fn write<'a>(&'a mut self, address: A, write: &'a [u8]) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + Self::setup(addr)?; + self.write_async_internal(write.iter().copied(), true).await + } + async fn write_read<'a>( + &'a mut self, + address: A, + write: &'a [u8], + read: &'a mut [u8], + ) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + Self::setup(addr)?; + self.write_async_internal(write.iter().cloned(), false).await?; + self.read_async_internal(read, false, true).await + } + async fn transaction<'a, 'b>( + &'a mut self, + address: A, + operations: &'a mut [Operation<'b>], + ) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + let mut iterator = operations.iter_mut(); + + while let Some(op) = iterator.next() { + let last = iterator.len() == 0; + + match op { + Operation::Read(buffer) => { + Self::setup(addr)?; + self.read_async_internal(buffer, false, last).await?; + } + Operation::Write(buffer) => { + Self::setup(addr)?; + self.write_async_internal(buffer.into_iter().cloned(), last).await?; + } + } + } + Ok(()) + } + } +} fn i2c_reserved_addr(addr: u16) -> bool { (addr & 0x78) == 0 || (addr & 0x78) == 0x78 @@ -458,6 +786,7 @@ fn i2c_reserved_addr(addr: u16) -> bool { mod sealed { use embassy_cortex_m::interrupt::Interrupt; + use embassy_sync::waitqueue::AtomicWaker; pub trait Instance { const TX_DREQ: u8; @@ -466,6 +795,8 @@ mod sealed { type Interrupt: Interrupt; fn regs() -> crate::pac::i2c::I2c; + fn reset() -> crate::pac::resets::regs::Peripherals; + fn waker() -> &'static AtomicWaker; } pub trait Mode {} @@ -492,23 +823,38 @@ impl_mode!(Async); pub trait Instance: sealed::Instance {} macro_rules! impl_instance { - ($type:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { + ($type:ident, $irq:ident, $reset:ident, $tx_dreq:expr, $rx_dreq:expr) => { impl sealed::Instance for peripherals::$type { const TX_DREQ: u8 = $tx_dreq; const RX_DREQ: u8 = $rx_dreq; type Interrupt = crate::interrupt::$irq; + #[inline] fn regs() -> pac::i2c::I2c { pac::$type } + + #[inline] + fn reset() -> pac::resets::regs::Peripherals { + let mut ret = pac::resets::regs::Peripherals::default(); + ret.$reset(true); + ret + } + + #[inline] + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + + &WAKER + } } impl Instance for peripherals::$type {} }; } -impl_instance!(I2C0, I2C0_IRQ, 32, 33); -impl_instance!(I2C1, I2C1_IRQ, 34, 35); +impl_instance!(I2C0, I2C0_IRQ, set_i2c0, 32, 33); +impl_instance!(I2C1, I2C1_IRQ, set_i2c1, 34, 35); pub trait SdaPin: sealed::SdaPin + crate::gpio::Pin {} pub trait SclPin: sealed::SclPin + crate::gpio::Pin {} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index e784399d4..9e99b2fbb 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -1,15 +1,28 @@ #![no_std] -#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; + mod intrinsics; +pub mod adc; pub mod dma; pub mod gpio; pub mod i2c; pub mod interrupt; + +#[cfg(feature = "pio")] +pub mod pio; +#[cfg(feature = "pio")] +pub mod pio_instr_util; +#[cfg(feature = "pio")] +pub mod relocate; + pub mod rom_data; pub mod rtc; pub mod spi; @@ -19,8 +32,11 @@ pub mod uart; #[cfg(feature = "nightly")] pub mod usb; -mod clocks; +pub mod clocks; +pub mod flash; +pub mod multicore; mod reset; +pub mod watchdog; // Reexports @@ -95,6 +111,17 @@ embassy_hal_common::peripherals! { USB, RTC, + + FLASH, + + ADC, + + CORE1, + + PIO0, + PIO1, + + WATCHDOG, } #[link_section = ".boot2"] diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs new file mode 100644 index 000000000..e51fc218a --- /dev/null +++ b/embassy-rp/src/multicore.rs @@ -0,0 +1,304 @@ +//! Multicore support +//! +//! This module handles setup of the 2nd cpu core on the rp2040, which we refer to as core1. +//! It provides functionality for setting up the stack, and starting core1. +//! +//! The entrypoint for core1 can be any function that never returns, including closures. +//! +//! Enable the `critical-section-impl` feature in embassy-rp when sharing data across cores using +//! the `embassy-sync` primitives and `CriticalSectionRawMutex`. +//! +//! # Usage +//! ```no_run +//! static mut CORE1_STACK: Stack<4096> = Stack::new(); +//! static EXECUTOR0: StaticCell = StaticCell::new(); +//! static EXECUTOR1: StaticCell = StaticCell::new(); +//! +//! #[cortex_m_rt::entry] +//! fn main() -> ! { +//! let p = embassy_rp::init(Default::default()); +//! +//! spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { +//! let executor1 = EXECUTOR1.init(Executor::new()); +//! executor1.run(|spawner| unwrap!(spawner.spawn(core1_task()))); +//! }); +//! +//! let executor0 = EXECUTOR0.init(Executor::new()); +//! executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +//! } +//! ``` + +use core::mem::ManuallyDrop; +use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; + +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::peripherals::CORE1; +use crate::{interrupt, pac}; + +const PAUSE_TOKEN: u32 = 0xDEADBEEF; +const RESUME_TOKEN: u32 = !0xDEADBEEF; +static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); + +#[inline(always)] +fn install_stack_guard(stack_bottom: *mut usize) { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Trap if MPU is already configured + if core.MPU.ctrl.read() != 0 { + cortex_m::asm::udf(); + } + + // The minimum we can protect is 32 bytes on a 32 byte boundary, so round up which will + // just shorten the valid stack range a tad. + let addr = (stack_bottom as u32 + 31) & !31; + // Mask is 1 bit per 32 bytes of the 256 byte range... clear the bit for the segment we want + let subregion_select = 0xff ^ (1 << ((addr >> 5) & 7)); + unsafe { + core.MPU.ctrl.write(5); // enable mpu with background default map + core.MPU.rbar.write((addr & !0xff) | 0x8); + core.MPU.rasr.write( + 1 // enable region + | (0x7 << 1) // size 2^(7 + 1) = 256 + | (subregion_select << 8) + | 0x10000000, // XN = disable instruction fetch; no other bits means no permissions + ); + } +} + +#[inline(always)] +fn core1_setup(stack_bottom: *mut usize) { + install_stack_guard(stack_bottom); +} + +/// Data type for a properly aligned stack of N bytes +#[repr(C, align(32))] +pub struct Stack { + /// Memory to be used for the stack + pub mem: [u8; SIZE], +} + +impl Stack { + /// Construct a stack of length SIZE, initialized to 0 + pub const fn new() -> Stack { + Stack { mem: [0_u8; SIZE] } + } +} + +#[interrupt] +#[link_section = ".data.ram_func"] +unsafe fn SIO_IRQ_PROC1() { + 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 + F: FnOnce() -> bad::Never + Send + 'static, +{ + // The first two ignored `u64` parameters are there to take up all of the registers, + // which means that the rest of the arguments are taken from the stack, + // where we're able to put them from core 0. + extern "C" fn core1_startup bad::Never>( + _: u64, + _: u64, + entry: &mut ManuallyDrop, + stack_bottom: *mut usize, + ) -> ! { + core1_setup(stack_bottom); + let entry = unsafe { ManuallyDrop::take(entry) }; + // Signal that it's safe for core 0 to get rid of the original value now. + fifo_write(1); + + IS_CORE1_INIT.store(true, Ordering::Release); + // Enable fifo interrupt on CORE1 for `pause` functionality. + let irq = unsafe { interrupt::SIO_IRQ_PROC1::steal() }; + irq.enable(); + + entry() + } + + // Reset the core + unsafe { + let psm = pac::PSM; + psm.frce_off().modify(|w| w.set_proc1(true)); + while !psm.frce_off().read().proc1() { + cortex_m::asm::nop(); + } + psm.frce_off().modify(|w| w.set_proc1(false)); + } + + let mem = unsafe { core::slice::from_raw_parts_mut(stack.mem.as_mut_ptr() as *mut usize, stack.mem.len() / 4) }; + + // Set up the stack + let mut stack_ptr = unsafe { mem.as_mut_ptr().add(mem.len()) }; + + // We don't want to drop this, since it's getting moved to the other core. + let mut entry = ManuallyDrop::new(entry); + + // Push the arguments to `core1_startup` onto the stack. + unsafe { + // Push `stack_bottom`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut usize>().write(mem.as_mut_ptr()); + + // Push `entry`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<&mut ManuallyDrop>().write(&mut entry); + } + + // Make sure the compiler does not reorder the stack writes after to after the + // below FIFO writes, which would result in them not being seen by the second + // core. + // + // From the compiler perspective, this doesn't guarantee that the second core + // actually sees those writes. However, we know that the RP2040 doesn't have + // memory caches, and writes happen in-order. + compiler_fence(Ordering::Release); + + let p = unsafe { cortex_m::Peripherals::steal() }; + let vector_table = p.SCB.vtor.read(); + + // After reset, core 1 is waiting to receive commands over FIFO. + // This is the sequence to have it jump to some code. + let cmd_seq = [ + 0, + 0, + 1, + vector_table as usize, + stack_ptr as usize, + core1_startup:: as usize, + ]; + + let mut seq = 0; + let mut fails = 0; + loop { + let cmd = cmd_seq[seq] as u32; + if cmd == 0 { + fifo_drain(); + cortex_m::asm::sev(); + } + fifo_write(cmd); + + let response = fifo_read(); + if cmd == response { + seq += 1; + } else { + seq = 0; + fails += 1; + if fails > 16 { + // The second core isn't responding, and isn't going to take the entrypoint + panic!("CORE1 not responding"); + } + } + if seq >= cmd_seq.len() { + break; + } + } + + // Wait until the other core has copied `entry` before returning. + fifo_read(); +} + +/// Pause execution on CORE1. +pub fn pause_core1() { + if IS_CORE1_INIT.load(Ordering::Acquire) { + fifo_write(PAUSE_TOKEN); + // Wait for CORE1 to signal it has paused execution. + while fifo_read() != PAUSE_TOKEN {} + } +} + +/// Resume CORE1 execution. +pub fn resume_core1() { + if IS_CORE1_INIT.load(Ordering::Acquire) { + fifo_write(RESUME_TOKEN); + // Wait for CORE1 to signal it has resumed execution. + while fifo_read() != RESUME_TOKEN {} + } +} + +// Push a value to the inter-core FIFO, block until space is available +#[inline(always)] +fn fifo_write(value: u32) { + unsafe { + let sio = pac::SIO; + // Wait for the FIFO to have enough space + while !sio.fifo().st().read().rdy() { + cortex_m::asm::nop(); + } + sio.fifo().wr().write_value(value); + } + // Fire off an event to the other core. + // This is required as the other core may be `wfe` (waiting for event) + cortex_m::asm::sev(); +} + +// Pop a value from inter-core FIFO, block until available +#[inline(always)] +fn fifo_read() -> u32 { + unsafe { + let sio = pac::SIO; + // Wait until FIFO has data + while !sio.fifo().st().read().vld() { + cortex_m::asm::nop(); + } + sio.fifo().rd().read() + } +} + +// Pop a value from inter-core FIFO, `wfe` until available +#[inline(always)] +fn fifo_read_wfe() -> u32 { + unsafe { + let sio = pac::SIO; + // Wait until FIFO has data + while !sio.fifo().st().read().vld() { + cortex_m::asm::wfe(); + } + sio.fifo().rd().read() + } +} + +// Drain inter-core FIFO +#[inline(always)] +fn fifo_drain() { + unsafe { + let sio = pac::SIO; + while sio.fifo().st().read().vld() { + let _ = sio.fifo().rd().read(); + } + } +} + +// https://github.com/nvzqz/bad-rs/blob/master/src/never.rs +mod bad { + pub(crate) type Never = ::Output; + + pub trait HasOutput { + type Output; + } + + impl HasOutput for fn() -> O { + type Output = O; + } + + type F = fn() -> !; +} diff --git a/embassy-rp/src/pio.rs b/embassy-rp/src/pio.rs new file mode 100644 index 000000000..1a067e54f --- /dev/null +++ b/embassy-rp/src/pio.rs @@ -0,0 +1,1259 @@ +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin as FuturePin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; +use embassy_hal_common::PeripheralRef; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::dma::{self, Channel, Transfer}; +use crate::gpio::sealed::Pin as SealedPin; +use crate::gpio::{Drive, Pin, Pull, SlewRate}; +use crate::pac::dma::vals::{DataSize, TreqSel}; +use crate::{interrupt, pac, peripherals}; + +const PIOS: [&pac::pio::Pio; 2] = [&pac::PIO0, &pac::PIO1]; +const NEW_AW: AtomicWaker = AtomicWaker::new(); +const PIO_WAKERS_INIT: [AtomicWaker; 4] = [NEW_AW; 4]; +static FIFO_OUT_WAKERS: [[AtomicWaker; 4]; 2] = [PIO_WAKERS_INIT; 2]; +static FIFO_IN_WAKERS: [[AtomicWaker; 4]; 2] = [PIO_WAKERS_INIT; 2]; +static IRQ_WAKERS: [[AtomicWaker; 4]; 2] = [PIO_WAKERS_INIT; 2]; + +pub enum FifoJoin { + /// Both TX and RX fifo is enabled + Duplex, + /// Rx fifo twice as deep. TX fifo disabled + RxOnly, + /// Tx fifo twice as deep. RX fifo disabled + TxOnly, +} + +#[derive(PartialEq)] +pub enum ShiftDirection { + Right = 1, + Left = 0, +} + +const RXNEMPTY_MASK: u32 = 1 << 0; +const TXNFULL_MASK: u32 = 1 << 4; +const SMIRQ_MASK: u32 = 1 << 8; + +#[interrupt] +unsafe fn PIO0_IRQ_1() { + use crate::pac; + let ints = pac::PIO0.irqs(1).ints().read().0; + let inte = pac::PIO0.irqs(1).inte(); + for i in 0..4 { + // Check RXNEMPTY + if ints & (RXNEMPTY_MASK << i) != 0 { + inte.modify(|m| { + m.0 &= !(RXNEMPTY_MASK << i); + }); + FIFO_IN_WAKERS[0][i].wake(); + } + // Check IRQ flgs + if ints & (SMIRQ_MASK << i) != 0 { + inte.modify(|m| { + m.0 &= !(SMIRQ_MASK << i); + }); + IRQ_WAKERS[0][i].wake(); + } + } +} + +#[interrupt] +unsafe fn PIO1_IRQ_1() { + use crate::pac; + let ints = pac::PIO1.irqs(1).ints().read().0; + let inte = pac::PIO1.irqs(1).inte(); + for i in 0..4 { + // Check all RXNEMPTY + if ints & (RXNEMPTY_MASK << i) != 0 { + inte.modify(|m| { + m.0 &= !(RXNEMPTY_MASK << i); + }); + FIFO_IN_WAKERS[1][i].wake(); + } + // Check IRQ flgs + if ints & (SMIRQ_MASK << i) != 0 { + inte.modify(|m| { + m.0 &= !(SMIRQ_MASK << i); + }); + IRQ_WAKERS[1][i].wake(); + } + } +} + +#[interrupt] +unsafe fn PIO0_IRQ_0() { + use crate::pac; + let ints = pac::PIO0.irqs(0).ints().read().0; + let inte = pac::PIO0.irqs(0).inte(); + //debug!("!{:04x}",ints); + // Check all TXNFULL + for i in 0..4 { + if ints & (TXNFULL_MASK << i) != 0 { + inte.modify(|m| { + m.0 &= !(TXNFULL_MASK << i); + }); + FIFO_OUT_WAKERS[0][i].wake(); + } + } +} + +#[interrupt] +unsafe fn PIO1_IRQ_0() { + use crate::pac; + let ints = pac::PIO1.irqs(0).ints().read().0; + let inte = pac::PIO1.irqs(0).inte(); + // Check all TXNFULL + for i in 0..4 { + if ints & (TXNFULL_MASK << i) != 0 { + inte.modify(|m| { + m.0 &= !(TXNFULL_MASK << i); + }); + FIFO_OUT_WAKERS[1][i].wake(); + } + } +} + +/// Future that waits for TX-FIFO to become writable +pub struct FifoOutFuture<'a, PIO: PioInstance, SM: PioStateMachine + Unpin> { + sm: &'a mut SM, + pio: PhantomData, + value: u32, +} + +impl<'a, PIO: PioInstance, SM: PioStateMachine + Unpin> FifoOutFuture<'a, PIO, SM> { + pub fn new(sm: &'a mut SM, value: u32) -> Self { + unsafe { + critical_section::with(|_| { + let irq = PIO::IrqOut::steal(); + irq.set_priority(interrupt::Priority::P3); + + irq.enable(); + }); + } + FifoOutFuture { + sm, + pio: PhantomData::default(), + value, + } + } +} + +impl<'d, PIO: PioInstance, SM: PioStateMachine + Unpin> Future for FifoOutFuture<'d, PIO, SM> { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + let value = self.value; + if self.get_mut().sm.try_push_tx(value) { + Poll::Ready(()) + } else { + FIFO_OUT_WAKERS[PIO::PIO_NO as usize][SM::Sm::SM_NO as usize].register(cx.waker()); + unsafe { + let irq = PIO::IrqOut::steal(); + irq.disable(); + critical_section::with(|_| { + PIOS[PIO::PIO_NO as usize].irqs(0).inte().modify(|m| { + m.0 |= TXNFULL_MASK << SM::Sm::SM_NO; + }); + }); + irq.enable(); + } + // debug!("Pending"); + Poll::Pending + } + } +} + +impl<'d, PIO: PioInstance, SM: PioStateMachine + Unpin> Drop for FifoOutFuture<'d, PIO, SM> { + fn drop(&mut self) { + unsafe { + critical_section::with(|_| { + PIOS[PIO::PIO_NO as usize].irqs(0).inte().modify(|m| { + m.0 &= !(TXNFULL_MASK << SM::Sm::SM_NO); + }); + }); + } + } +} + +/// Future that waits for RX-FIFO to become readable +pub struct FifoInFuture<'a, PIO: PioInstance, SM: PioStateMachine> { + sm: &'a mut SM, + pio: PhantomData, +} + +impl<'a, PIO: PioInstance, SM: PioStateMachine> FifoInFuture<'a, PIO, SM> { + pub fn new(sm: &'a mut SM) -> Self { + unsafe { + critical_section::with(|_| { + let irq = PIO::IrqIn::steal(); + irq.set_priority(interrupt::Priority::P3); + + irq.enable(); + }); + } + FifoInFuture { + sm, + pio: PhantomData::default(), + } + } +} + +impl<'d, PIO: PioInstance, SM: PioStateMachine> Future for FifoInFuture<'d, PIO, SM> { + type Output = u32; + fn poll(mut self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + if let Some(v) = self.sm.try_pull_rx() { + Poll::Ready(v) + } else { + FIFO_IN_WAKERS[PIO::PIO_NO as usize][SM::Sm::SM_NO as usize].register(cx.waker()); + unsafe { + let irq = PIO::IrqIn::steal(); + irq.disable(); + critical_section::with(|_| { + PIOS[PIO::PIO_NO as usize].irqs(1).inte().modify(|m| { + m.0 |= RXNEMPTY_MASK << SM::Sm::SM_NO; + }); + }); + irq.enable(); + } + //debug!("Pending"); + Poll::Pending + } + } +} + +impl<'d, PIO: PioInstance, SM: PioStateMachine> Drop for FifoInFuture<'d, PIO, SM> { + fn drop(&mut self) { + unsafe { + critical_section::with(|_| { + PIOS[PIO::PIO_NO as usize].irqs(1).inte().modify(|m| { + m.0 &= !(RXNEMPTY_MASK << SM::Sm::SM_NO); + }); + }); + } + } +} + +/// Future that waits for IRQ +pub struct IrqFuture { + pio: PhantomData, + irq_no: u8, +} + +impl<'a, PIO: PioInstance> IrqFuture { + pub fn new(irq_no: u8) -> Self { + unsafe { + critical_section::with(|_| { + let irq = PIO::IrqSm::steal(); + irq.set_priority(interrupt::Priority::P3); + + irq.enable(); + }); + } + IrqFuture { + pio: PhantomData::default(), + irq_no, + } + } +} + +impl<'d, PIO: PioInstance> Future for IrqFuture { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + + // Check if IRQ flag is already set + if critical_section::with(|_| unsafe { + let irq_flags = PIOS[PIO::PIO_NO as usize].irq(); + if irq_flags.read().0 & (1 << self.irq_no) != 0 { + irq_flags.write(|m| { + m.0 = 1 << self.irq_no; + }); + true + } else { + false + } + }) { + return Poll::Ready(()); + } + + IRQ_WAKERS[PIO::PIO_NO as usize][self.irq_no as usize].register(cx.waker()); + unsafe { + let irq = PIO::IrqSm::steal(); + irq.disable(); + critical_section::with(|_| { + PIOS[PIO::PIO_NO as usize].irqs(1).inte().modify(|m| { + m.0 |= SMIRQ_MASK << self.irq_no; + }); + }); + irq.enable(); + } + Poll::Pending + } +} + +impl<'d, PIO: PioInstance> Drop for IrqFuture { + fn drop(&mut self) { + unsafe { + critical_section::with(|_| { + PIOS[PIO::PIO_NO as usize].irqs(1).inte().modify(|m| { + m.0 &= !(SMIRQ_MASK << self.irq_no); + }); + }); + } + } +} + +pub struct PioPin { + pin_bank: u8, + pio: PhantomData, +} + +impl PioPin { + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + unsafe { + self.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2MA, + Drive::_4mA => pac::pads::vals::Drive::_4MA, + Drive::_8mA => pac::pads::vals::Drive::_8MA, + Drive::_12mA => pac::pads::vals::Drive::_12MA, + }); + }); + } + } + + // Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + unsafe { + self.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); + }); + } + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + unsafe { + self.pad_ctrl().modify(|w| match pull { + Pull::Up => w.set_pue(true), + Pull::Down => w.set_pde(true), + Pull::None => {} + }); + } + } + + /// Set the pin's pull. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + unsafe { + self.pad_ctrl().modify(|w| { + w.set_schmitt(enable); + }); + } + } + + pub fn set_input_sync_bypass<'a>(&mut self, bypass: bool) { + let mask = 1 << self.pin(); + unsafe { + PIOS[PIO::PIO_NO as usize] + .input_sync_bypass() + .modify(|w| *w = if bypass { *w & !mask } else { *w | mask }); + } + } + + pub fn pin(&self) -> u8 { + self._pin() + } +} + +impl SealedPin for PioPin { + fn pin_bank(&self) -> u8 { + self.pin_bank + } +} + +pub struct PioStateMachineInstance { + pio: PhantomData, + sm: PhantomData, +} + +impl PioStateMachine for PioStateMachineInstance { + type Pio = PIO; + type Sm = SM; +} + +pub trait PioStateMachine: Sized + Unpin { + type Pio: PioInstance; + type Sm: SmInstance; + fn pio_no(&self) -> u8 { + let _ = self; + Self::Pio::PIO_NO + } + + fn sm_no(&self) -> u8 { + Self::Sm::SM_NO + } + + fn restart(&mut self) { + let _ = self; + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .ctrl() + .modify(|w| w.set_sm_restart(1u8 << Self::Sm::SM_NO)); + } + } + fn set_enable(&mut self, enable: bool) { + let _ = self; + let mask = 1u8 << Self::Sm::SM_NO; + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .ctrl() + .modify(|w| w.set_sm_enable((w.sm_enable() & !mask) | (if enable { mask } else { 0 }))); + } + } + + fn is_enabled(&self) -> bool { + let _ = self; + unsafe { PIOS[Self::Pio::PIO_NO as usize].ctrl().read().sm_enable() & (1u8 << Self::Sm::SM_NO) != 0 } + } + + fn is_tx_empty(&self) -> bool { + let _ = self; + unsafe { PIOS[Self::Pio::PIO_NO as usize].fstat().read().txempty() & (1u8 << Self::Sm::SM_NO) != 0 } + } + fn is_tx_full(&self) -> bool { + let _ = self; + unsafe { PIOS[Self::Pio::PIO_NO as usize].fstat().read().txfull() & (1u8 << Self::Sm::SM_NO) != 0 } + } + + fn is_rx_empty(&self) -> bool { + let _ = self; + unsafe { PIOS[Self::Pio::PIO_NO as usize].fstat().read().rxempty() & (1u8 << Self::Sm::SM_NO) != 0 } + } + fn is_rx_full(&self) -> bool { + let _ = self; + unsafe { PIOS[Self::Pio::PIO_NO as usize].fstat().read().rxfull() & (1u8 << Self::Sm::SM_NO) != 0 } + } + + fn tx_level(&self) -> u8 { + unsafe { + let flevel = PIOS[Self::Pio::PIO_NO as usize].flevel().read().0; + (flevel >> (Self::Sm::SM_NO * 8)) as u8 & 0x0f + } + } + + fn rx_level(&self) -> u8 { + unsafe { + let flevel = PIOS[Self::Pio::PIO_NO as usize].flevel().read().0; + (flevel >> (Self::Sm::SM_NO * 8 + 4)) as u8 & 0x0f + } + } + + fn push_tx(&mut self, v: u32) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .txf(Self::Sm::SM_NO as usize) + .write_value(v); + } + } + + fn try_push_tx(&mut self, v: u32) -> bool { + if self.is_tx_full() { + return false; + } + self.push_tx(v); + true + } + + fn pull_rx(&mut self) -> u32 { + unsafe { PIOS[Self::Pio::PIO_NO as usize].rxf(Self::Sm::SM_NO as usize).read() } + } + + fn try_pull_rx(&mut self) -> Option { + if self.is_rx_empty() { + return None; + } + Some(self.pull_rx()) + } + + fn set_clkdiv(&mut self, div_x_256: u32) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .clkdiv() + .write(|w| w.0 = div_x_256 << 8); + } + } + + fn get_clkdiv(&self) -> u32 { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .clkdiv() + .read() + .0 + >> 8 + } + } + + fn clkdiv_restart(&mut self) { + let _ = self; + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .ctrl() + .modify(|w| w.set_clkdiv_restart(1u8 << Self::Sm::SM_NO)); + } + } + + fn set_side_enable(&self, enable: bool) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .modify(|w| w.set_side_en(enable)); + } + } + + fn is_side_enabled(&self) -> bool { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .read() + .side_en() + } + } + + fn set_side_pindir(&mut self, pindir: bool) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .modify(|w| w.set_side_pindir(pindir)); + } + } + + fn is_side_pindir(&self) -> bool { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .read() + .side_pindir() + } + } + + fn set_jmp_pin(&mut self, pin: u8) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .modify(|w| w.set_jmp_pin(pin)); + } + } + + fn get_jmp_pin(&mut self) -> u8 { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .read() + .jmp_pin() + } + } + + fn set_wrap(&self, source: u8, target: u8) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .modify(|w| { + w.set_wrap_top(source); + w.set_wrap_bottom(target) + }); + } + } + + /// Get wrapping addresses. Returns (source, target). + fn get_wrap(&self) -> (u8, u8) { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .execctrl() + .read(); + (r.wrap_top(), r.wrap_bottom()) + } + } + + fn set_fifo_join(&mut self, join: FifoJoin) { + let (rx, tx) = match join { + FifoJoin::Duplex => (false, false), + FifoJoin::RxOnly => (true, false), + FifoJoin::TxOnly => (false, true), + }; + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .modify(|w| { + w.set_fjoin_rx(rx); + w.set_fjoin_tx(tx) + }); + } + } + fn get_fifo_join(&self) -> FifoJoin { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .read(); + // Ignores the invalid state when both bits are set + if r.fjoin_rx() { + FifoJoin::RxOnly + } else if r.fjoin_tx() { + FifoJoin::TxOnly + } else { + FifoJoin::Duplex + } + } + } + + fn clear_fifos(&mut self) { + // Toggle FJOIN_RX to flush FIFOs + unsafe { + let shiftctrl = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl(); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + } + } + + fn set_pull_threshold(&mut self, threshold: u8) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .modify(|w| w.set_pull_thresh(threshold)); + } + } + + fn get_pull_threshold(&self) -> u8 { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .read(); + r.pull_thresh() + } + } + fn set_push_threshold(&mut self, threshold: u8) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .modify(|w| w.set_push_thresh(threshold)); + } + } + + fn get_push_threshold(&self) -> u8 { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .read(); + r.push_thresh() + } + } + + fn set_out_shift_dir(&mut self, dir: ShiftDirection) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .modify(|w| w.set_out_shiftdir(dir == ShiftDirection::Right)); + } + } + fn get_out_shiftdir(&self) -> ShiftDirection { + unsafe { + if PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .read() + .out_shiftdir() + { + ShiftDirection::Right + } else { + ShiftDirection::Left + } + } + } + + fn set_in_shift_dir(&mut self, dir: ShiftDirection) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .modify(|w| w.set_in_shiftdir(dir == ShiftDirection::Right)); + } + } + fn get_in_shiftdir(&self) -> ShiftDirection { + unsafe { + if PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .read() + .in_shiftdir() + { + ShiftDirection::Right + } else { + ShiftDirection::Left + } + } + } + + fn set_autopull(&mut self, auto: bool) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .modify(|w| w.set_autopull(auto)); + } + } + + fn is_autopull(&self) -> bool { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .read() + .autopull() + } + } + + fn set_autopush(&mut self, auto: bool) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .modify(|w| w.set_autopush(auto)); + } + } + + fn is_autopush(&self) -> bool { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .shiftctrl() + .read() + .autopush() + } + } + + fn get_addr(&self) -> u8 { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .addr() + .read(); + r.addr() + } + } + fn set_sideset_count(&mut self, count: u8) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .modify(|w| w.set_sideset_count(count)); + } + } + + fn get_sideset_count(&self) -> u8 { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .read(); + r.sideset_count() + } + } + + fn make_pio_pin(&self, pin: impl Pin) -> PioPin { + unsafe { + pin.io().ctrl().write(|w| { + w.set_funcsel( + if Self::Pio::PIO_NO == 1 { + pac::io::vals::Gpio0ctrlFuncsel::PIO1_0 + } else { + // PIO == 0 + pac::io::vals::Gpio0ctrlFuncsel::PIO0_0 + } + .0, + ); + }); + } + PioPin { + pin_bank: pin.pin_bank(), + pio: PhantomData::default(), + } + } + + fn set_sideset_base_pin(&mut self, base_pin: &PioPin) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .modify(|w| w.set_sideset_base(base_pin.pin())); + } + } + + fn get_sideset_base(&self) -> u8 { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .read(); + r.sideset_base() + } + } + + /// Set the range of out pins affected by a set instruction. + fn set_set_range(&mut self, base: u8, count: u8) { + assert!(base + count < 32); + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .modify(|w| { + w.set_set_base(base); + w.set_set_count(count) + }); + } + } + + /// Get the range of out pins affected by a set instruction. Returns (base, count). + fn get_set_range(&self) -> (u8, u8) { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .read(); + (r.set_base(), r.set_count()) + } + } + + fn set_in_base_pin(&mut self, base: &PioPin) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .modify(|w| w.set_in_base(base.pin())); + } + } + + fn get_in_base(&self) -> u8 { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .read(); + r.in_base() + } + } + + fn set_out_range(&mut self, base: u8, count: u8) { + assert!(base + count < 32); + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .modify(|w| { + w.set_out_base(base); + w.set_out_count(count) + }); + } + } + + /// Get the range of out pins affected by a set instruction. Returns (base, count). + fn get_out_range(&self) -> (u8, u8) { + unsafe { + let r = PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .pinctrl() + .read(); + (r.out_base(), r.out_count()) + } + } + + fn set_out_pins<'a, 'b: 'a>(&'a mut self, pins: &'b [&PioPin]) { + let count = pins.len(); + assert!(count >= 1); + let start = pins[0].pin() as usize; + assert!(start + pins.len() <= 32); + for i in 0..count { + assert!(pins[i].pin() as usize == start + i, "Pins must be sequential"); + } + self.set_out_range(start as u8, count as u8); + } + + fn set_set_pins<'a, 'b: 'a>(&'a mut self, pins: &'b [&PioPin]) { + let count = pins.len(); + assert!(count >= 1); + let start = pins[0].pin() as usize; + assert!(start + pins.len() <= 32); + for i in 0..count { + assert!(pins[i].pin() as usize == start + i, "Pins must be sequential"); + } + self.set_set_range(start as u8, count as u8); + } + + fn get_current_instr() -> u32 { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .instr() + .read() + .0 + } + } + + fn exec_instr(&mut self, instr: u16) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .sm(Self::Sm::SM_NO as usize) + .instr() + .write(|w| w.set_instr(instr)); + } + } + + fn write_instr(&mut self, start: usize, instrs: I) + where + I: Iterator, + { + let _ = self; + write_instr( + Self::Pio::PIO_NO, + start, + instrs, + MEM_USED_BY_STATEMACHINE | Self::Sm::SM_NO as u32, + ); + } + + fn is_irq_set(&self, irq_no: u8) -> bool { + assert!(irq_no < 8); + unsafe { + let irq_flags = PIOS[Self::Pio::PIO_NO as usize].irq(); + irq_flags.read().0 & (1 << irq_no) != 0 + } + } + + fn clear_irq(&mut self, irq_no: usize) { + assert!(irq_no < 8); + unsafe { PIOS[Self::Pio::PIO_NO as usize].irq().write(|w| w.set_irq(1 << irq_no)) } + } + + fn wait_push<'a>(&'a mut self, value: u32) -> FifoOutFuture<'a, Self::Pio, Self> { + FifoOutFuture::new(self, value) + } + + fn wait_pull<'a>(&'a mut self) -> FifoInFuture<'a, Self::Pio, Self> { + FifoInFuture::new(self) + } + + fn wait_irq(&self, irq_no: u8) -> IrqFuture { + IrqFuture::new(irq_no) + } + + fn has_tx_stalled(&self) -> bool { + unsafe { + let fdebug = PIOS[Self::Pio::PIO_NO as usize].fdebug(); + let ret = fdebug.read().txstall() & (1 << Self::Sm::SM_NO) != 0; + fdebug.write(|w| w.set_txstall(1 << Self::Sm::SM_NO)); + ret + } + } + + fn has_tx_overflowed(&self) -> bool { + unsafe { + let fdebug = PIOS[Self::Pio::PIO_NO as usize].fdebug(); + let ret = fdebug.read().txover() & (1 << Self::Sm::SM_NO) != 0; + fdebug.write(|w| w.set_txover(1 << Self::Sm::SM_NO)); + ret + } + } + + fn has_rx_stalled(&self) -> bool { + unsafe { + let fdebug = PIOS[Self::Pio::PIO_NO as usize].fdebug(); + let ret = fdebug.read().rxstall() & (1 << Self::Sm::SM_NO) != 0; + fdebug.write(|w| w.set_rxstall(1 << Self::Sm::SM_NO)); + ret + } + } + + fn has_rx_underflowed(&self) -> bool { + unsafe { + let fdebug = PIOS[Self::Pio::PIO_NO as usize].fdebug(); + let ret = fdebug.read().rxunder() & (1 << Self::Sm::SM_NO) != 0; + fdebug.write(|w| w.set_rxunder(1 << Self::Sm::SM_NO)); + ret + } + } + + fn dma_push<'a, C: Channel>(&'a self, ch: PeripheralRef<'a, C>, data: &'a [u32]) -> Transfer<'a, C> { + unsafe { + dma::init(); + let pio_no = Self::Pio::PIO_NO; + let sm_no = Self::Sm::SM_NO; + let p = ch.regs(); + p.read_addr().write_value(data.as_ptr() as u32); + p.write_addr() + .write_value(PIOS[pio_no as usize].txf(sm_no as usize).ptr() as u32); + p.trans_count().write_value(data.len() as u32); + p.ctrl_trig().write(|w| { + // Set TX DREQ for this statemachine + w.set_treq_sel(TreqSel(pio_no * 8 + sm_no)); + w.set_data_size(DataSize::SIZE_WORD); + w.set_chain_to(ch.number()); + w.set_incr_read(true); + w.set_incr_write(false); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + } + Transfer::new(ch) + } + + fn dma_pull<'a, C: Channel>(&'a self, ch: PeripheralRef<'a, C>, data: &'a mut [u32]) -> Transfer<'a, C> { + unsafe { + dma::init(); + let pio_no = Self::Pio::PIO_NO; + let sm_no = Self::Sm::SM_NO; + let p = ch.regs(); + p.write_addr().write_value(data.as_ptr() as u32); + p.read_addr() + .write_value(PIOS[pio_no as usize].rxf(sm_no as usize).ptr() as u32); + p.trans_count().write_value(data.len() as u32); + p.ctrl_trig().write(|w| { + // Set TX DREQ for this statemachine + w.set_treq_sel(TreqSel(pio_no * 8 + sm_no + 4)); + w.set_data_size(DataSize::SIZE_WORD); + w.set_chain_to(ch.number()); + w.set_incr_read(false); + w.set_incr_write(true); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + } + Transfer::new(ch) + } +} + +/* +This is a bit array containing 4 bits for every word in the PIO instruction memory. +*/ +// Bit 3-2 +//const MEM_USE_MASK: u32 = 0b1100; +const MEM_NOT_USED: u32 = 0b0000; +const MEM_USED_BY_STATEMACHINE: u32 = 0b0100; +const MEM_USED_BY_COMMON: u32 = 0b1000; + +// Bit 1-0 is the number of the state machine +//const MEM_STATE_MASK: u32 = 0b0011; + +// Should use mutex if running on multiple cores +static mut INSTR_MEM_STATUS: &'static mut [[u32; 4]; 2] = &mut [[0; 4]; 2]; + +fn instr_mem_get_status(pio_no: u8, addr: u8) -> u32 { + ((unsafe { INSTR_MEM_STATUS[pio_no as usize][(addr >> 3) as usize] }) >> ((addr & 0x07) * 4)) & 0xf +} + +fn instr_mem_set_status(pio_no: u8, addr: u8, status: u32) { + let w = unsafe { &mut INSTR_MEM_STATUS[pio_no as usize][(addr >> 3) as usize] }; + let shift = (addr & 0x07) * 4; + *w = (*w & !(0xf << shift)) | (status << shift); +} + +fn instr_mem_is_free(pio_no: u8, addr: u8) -> bool { + instr_mem_get_status(pio_no, addr) == MEM_NOT_USED +} + +pub struct PioCommonInstance { + pio: PhantomData, +} + +impl PioCommon for PioCommonInstance { + type Pio = PIO; +} + +fn write_instr(pio_no: u8, start: usize, instrs: I, mem_user: u32) +where + I: Iterator, +{ + for (i, instr) in instrs.enumerate() { + let addr = (i + start) as u8; + assert!( + instr_mem_is_free(pio_no, addr), + "Trying to write already used PIO instruction memory at {}", + addr + ); + unsafe { + PIOS[pio_no as usize].instr_mem(addr as usize).write(|w| { + w.set_instr_mem(instr); + }); + instr_mem_set_status(pio_no, addr, mem_user); + } + } +} + +pub trait PioCommon: Sized { + type Pio: PioInstance; + + fn write_instr(&mut self, start: usize, instrs: I) + where + I: Iterator, + { + let _ = self; + write_instr(Self::Pio::PIO_NO, start, instrs, MEM_USED_BY_COMMON); + } + + fn clear_irq(&mut self, irq_no: usize) { + assert!(irq_no < 8); + unsafe { PIOS[Self::Pio::PIO_NO as usize].irq().write(|w| w.set_irq(1 << irq_no)) } + } + + fn clear_irqs(&mut self, mask: u8) { + unsafe { PIOS[Self::Pio::PIO_NO as usize].irq().write(|w| w.set_irq(mask)) } + } + + fn force_irq(&mut self, irq_no: usize) { + assert!(irq_no < 8); + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .irq_force() + .write(|w| w.set_irq_force(1 << irq_no)) + } + } + + fn set_input_sync_bypass<'a>(&'a mut self, bypass: u32, mask: u32) { + unsafe { + PIOS[Self::Pio::PIO_NO as usize] + .input_sync_bypass() + .modify(|w| *w = (*w & !mask) | (bypass & mask)); + } + } + + fn get_input_sync_bypass(&self) -> u32 { + unsafe { PIOS[Self::Pio::PIO_NO as usize].input_sync_bypass().read() } + } +} + +// Identifies a specific state machine inside a PIO device +pub struct SmInstanceBase {} + +pub trait SmInstance: Unpin { + const SM_NO: u8; +} + +impl SmInstance for SmInstanceBase { + const SM_NO: u8 = SM_NO; +} + +pub trait PioPeripherial: Sized { + type Pio: PioInstance; + fn pio(&self) -> u8 { + let _ = self; + Self::Pio::PIO_NO + } + + fn split( + self, + ) -> ( + PioCommonInstance, + PioStateMachineInstance>, + PioStateMachineInstance>, + PioStateMachineInstance>, + PioStateMachineInstance>, + ) { + let _ = self; + ( + PioCommonInstance { + pio: PhantomData::default(), + }, + PioStateMachineInstance { + sm: PhantomData::default(), + pio: PhantomData::default(), + }, + PioStateMachineInstance { + sm: PhantomData::default(), + pio: PhantomData::default(), + }, + PioStateMachineInstance { + sm: PhantomData::default(), + pio: PhantomData::default(), + }, + PioStateMachineInstance { + sm: PhantomData::default(), + pio: PhantomData::default(), + }, + ) + } +} + +// Identifies a specific PIO device +pub struct PioInstanceBase {} + +pub trait PioInstance: Unpin { + const PIO_NO: u8; + type IrqOut: Interrupt; + type IrqIn: Interrupt; + type IrqSm: Interrupt; +} + +impl PioInstance for PioInstanceBase<0> { + const PIO_NO: u8 = 0; + type IrqOut = interrupt::PIO0_IRQ_0; + type IrqIn = interrupt::PIO0_IRQ_1; + type IrqSm = interrupt::PIO0_IRQ_1; +} + +impl PioInstance for PioInstanceBase<1> { + const PIO_NO: u8 = 1; + type IrqOut = interrupt::PIO1_IRQ_0; + type IrqIn = interrupt::PIO1_IRQ_1; + type IrqSm = interrupt::PIO1_IRQ_1; +} + +pub type Pio0 = PioInstanceBase<0>; +pub type Pio1 = PioInstanceBase<1>; + +pub type Sm0 = SmInstanceBase<0>; +pub type Sm1 = SmInstanceBase<1>; +pub type Sm2 = SmInstanceBase<2>; +pub type Sm3 = SmInstanceBase<3>; + +macro_rules! impl_pio_sm { + ($name:ident, $pio:expr) => { + impl PioPeripherial for peripherals::$name { + type Pio = PioInstanceBase<$pio>; + } + }; +} + +impl_pio_sm!(PIO0, 0); +impl_pio_sm!(PIO1, 1); diff --git a/embassy-rp/src/pio_instr_util.rs b/embassy-rp/src/pio_instr_util.rs new file mode 100644 index 000000000..ae26ff1dc --- /dev/null +++ b/embassy-rp/src/pio_instr_util.rs @@ -0,0 +1,90 @@ +use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestination}; + +use crate::pio::PioStateMachine; + +pub fn set_x(sm: &mut SM, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::X, + bit_count: 32, + } + .encode(); + sm.push_tx(value); + sm.exec_instr(OUT); +} + +pub fn get_x(sm: &mut SM) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::X, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + sm.pull_rx() +} + +pub fn set_y(sm: &mut SM, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::Y, + bit_count: 32, + } + .encode(); + sm.push_tx(value); + sm.exec_instr(OUT); +} + +pub fn get_y(sm: &mut SM) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::Y, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + + sm.pull_rx() +} + +pub fn set_pindir(sm: &mut SM, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINDIRS, + data, + } + .encode(); + sm.exec_instr(set); +} + +pub fn set_pin(sm: &mut SM, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINS, + data, + } + .encode(); + sm.exec_instr(set); +} + +pub fn set_out_pin(sm: &mut SM, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINS, + bit_count: 32, + } + .encode(); + sm.push_tx(data); + sm.exec_instr(OUT); +} +pub fn set_out_pindir(sm: &mut SM, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINDIRS, + bit_count: 32, + } + .encode(); + sm.push_tx(data); + sm.exec_instr(OUT); +} + +pub fn exec_jmp(sm: &mut SM, to_addr: u8) { + let jmp: u16 = InstructionOperands::JMP { + address: to_addr, + condition: JmpCondition::Always, + } + .encode(); + sm.exec_instr(jmp); +} diff --git a/embassy-rp/src/relocate.rs b/embassy-rp/src/relocate.rs new file mode 100644 index 000000000..f36170e03 --- /dev/null +++ b/embassy-rp/src/relocate.rs @@ -0,0 +1,77 @@ +use core::iter::Iterator; + +use pio::{Program, SideSet, Wrap}; + +pub struct CodeIterator<'a, I> +where + I: Iterator, +{ + iter: I, + offset: u8, +} + +impl<'a, I: Iterator> CodeIterator<'a, I> { + pub fn new(iter: I, offset: u8) -> CodeIterator<'a, I> { + CodeIterator { iter, offset } + } +} + +impl<'a, I> Iterator for CodeIterator<'a, I> +where + I: Iterator, +{ + type Item = u16; + fn next(&mut self) -> Option { + self.iter.next().and_then(|&instr| { + Some(if instr & 0b1110_0000_0000_0000 == 0 { + // this is a JMP instruction -> add offset to address + let address = (instr & 0b1_1111) as u8; + let address = address + self.offset; + assert!( + address < pio::RP2040_MAX_PROGRAM_SIZE as u8, + "Invalid JMP out of the program after offset addition" + ); + instr & (!0b11111) | address as u16 + } else { + instr + }) + }) + } +} + +pub struct RelocatedProgram<'a, const PROGRAM_SIZE: usize> { + program: &'a Program, + origin: u8, +} + +impl<'a, const PROGRAM_SIZE: usize> RelocatedProgram<'a, PROGRAM_SIZE> { + pub fn new(program: &Program) -> RelocatedProgram { + let origin = program.origin.unwrap_or(0); + RelocatedProgram { program, origin } + } + + pub fn new_with_origin(program: &Program, origin: u8) -> RelocatedProgram { + RelocatedProgram { program, origin } + } + + pub fn code(&'a self) -> CodeIterator<'a, core::slice::Iter<'a, u16>> { + CodeIterator::new(self.program.code.iter(), self.origin) + } + + pub fn wrap(&self) -> Wrap { + let wrap = self.program.wrap; + let origin = self.origin; + Wrap { + source: wrap.source + origin, + target: wrap.target + origin, + } + } + + pub fn side_set(&self) -> SideSet { + self.program.side_set + } + + pub fn origin(&self) -> u8 { + self.origin + } +} diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs index 7f3bbbe73..c173909c7 100644 --- a/embassy-rp/src/rtc/mod.rs +++ b/embassy-rp/src/rtc/mod.rs @@ -145,6 +145,8 @@ impl<'d, T: Instance> RealTimeClock<'d, T> { filter.write_setup_1(w); }); + self.inner.regs().inte().modify(|w| w.set_rtc(true)); + // Set the enable bit and check if it is set self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true)); while !self.inner.regs().irq_setup_0().read().match_active() { @@ -162,7 +164,7 @@ impl<'d, T: Instance> RealTimeClock<'d, T> { } } -/// Errors that can occur on methods on [RtcClock] +/// Errors that can occur on methods on [RealTimeClock] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RtcError { /// An invalid DateTime was given or stored on the hardware. diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index 754e2dd30..584370d56 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -473,6 +473,16 @@ impl_pin!(PIN_16, SPI0, MisoPin); impl_pin!(PIN_17, SPI0, CsPin); impl_pin!(PIN_18, SPI0, ClkPin); impl_pin!(PIN_19, SPI0, MosiPin); +impl_pin!(PIN_20, SPI0, MisoPin); +impl_pin!(PIN_21, SPI0, CsPin); +impl_pin!(PIN_22, SPI0, ClkPin); +impl_pin!(PIN_23, SPI0, MosiPin); +impl_pin!(PIN_24, SPI1, MisoPin); +impl_pin!(PIN_25, SPI1, CsPin); +impl_pin!(PIN_26, SPI1, ClkPin); +impl_pin!(PIN_27, SPI1, MosiPin); +impl_pin!(PIN_28, SPI1, MisoPin); +impl_pin!(PIN_29, SPI1, CsPin); macro_rules! impl_mode { ($name:ident) => { @@ -554,45 +564,33 @@ mod eh1 { #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eha { - use core::future::Future; - use super::*; impl<'d, T: Instance> embedded_hal_async::spi::SpiBusFlush for Spi<'d, T, Async> { - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async { Ok(()) } + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } impl<'d, T: Instance> embedded_hal_async::spi::SpiBusWrite for Spi<'d, T, Async> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(data) + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).await } } impl<'d, T: Instance> embedded_hal_async::spi::SpiBusRead for Spi<'d, T, Async> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, data: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(data) + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).await } } impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spi<'d, T, Async> { - type TransferFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer<'a>(&'a mut self, rx: &'a mut [u8], tx: &'a [u8]) -> Self::TransferFuture<'a> { - self.transfer(rx, tx) + async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> { + self.transfer(read, write).await } - type TransferInPlaceFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer_in_place<'a>(&'a mut self, words: &'a mut [u8]) -> Self::TransferInPlaceFuture<'a> { - self.transfer_in_place(words) + async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await } } } diff --git a/embassy-rp/src/timer.rs b/embassy-rp/src/timer.rs index 5215c0c0f..80efd779f 100644 --- a/embassy-rp/src/timer.rs +++ b/embassy-rp/src/timer.rs @@ -68,7 +68,7 @@ impl Driver for TimerDriver { }) } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { let n = alarm.id() as usize; critical_section::with(|cs| { let alarm = &self.alarms.borrow(cs)[n]; @@ -81,11 +81,16 @@ impl Driver for TimerDriver { unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; let now = self.now(); - - // If alarm timestamp has passed, trigger it instantly. - // This disarms it. if timestamp <= now { - self.trigger_alarm(n, cs); + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + unsafe { pac::TIMER.armed().write(|w| w.set_armed(1 << n)) } + + alarm.timestamp.set(u64::MAX); + + false + } else { + true } }) } diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index 87e16f0eb..0da5bfca1 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -1,338 +1,410 @@ use core::future::{poll_fn, Future}; -use core::task::{Poll, Waker}; +use core::slice; +use core::task::Poll; -use atomic_polyfill::{compiler_fence, Ordering}; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; -use embassy_hal_common::ring_buffer::RingBuffer; -use embassy_sync::waitqueue::WakerRegistration; +use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; +use embassy_hal_common::atomic_ring_buffer::RingBuffer; +use embassy_sync::waitqueue::AtomicWaker; use super::*; +use crate::RegExt; -pub struct State<'d, T: Instance>(StateStorage>); -impl<'d, T: Instance> State<'d, T> { +pub struct State { + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + rx_waker: AtomicWaker, + rx_buf: RingBuffer, +} + +impl State { pub const fn new() -> Self { - Self(StateStorage::new()) + Self { + rx_buf: RingBuffer::new(), + tx_buf: RingBuffer::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + } } } -pub struct RxState<'d, T: Instance>(StateStorage>); -impl<'d, T: Instance> RxState<'d, T> { - pub const fn new() -> Self { - Self(StateStorage::new()) - } -} - -pub struct TxState<'d, T: Instance>(StateStorage>); -impl<'d, T: Instance> TxState<'d, T> { - pub const fn new() -> Self { - Self(StateStorage::new()) - } -} - -struct RxStateInner<'d, T: Instance> { - phantom: PhantomData<&'d mut T>, - - waker: WakerRegistration, - buf: RingBuffer<'d>, -} - -struct TxStateInner<'d, T: Instance> { - phantom: PhantomData<&'d mut T>, - - waker: WakerRegistration, - buf: RingBuffer<'d>, -} - -struct FullStateInner<'d, T: Instance> { - rx: RxStateInner<'d, T>, - tx: TxStateInner<'d, T>, -} - -unsafe impl<'d, T: Instance> Send for RxStateInner<'d, T> {} -unsafe impl<'d, T: Instance> Sync for RxStateInner<'d, T> {} - -unsafe impl<'d, T: Instance> Send for TxStateInner<'d, T> {} -unsafe impl<'d, T: Instance> Sync for TxStateInner<'d, T> {} - -unsafe impl<'d, T: Instance> Send for FullStateInner<'d, T> {} -unsafe impl<'d, T: Instance> Sync for FullStateInner<'d, T> {} - pub struct BufferedUart<'d, T: Instance> { - inner: PeripheralMutex<'d, FullStateInner<'d, T>>, + rx: BufferedUartRx<'d, T>, + tx: BufferedUartTx<'d, T>, } pub struct BufferedUartRx<'d, T: Instance> { - inner: PeripheralMutex<'d, RxStateInner<'d, T>>, + phantom: PhantomData<&'d mut T>, } pub struct BufferedUartTx<'d, T: Instance> { - inner: PeripheralMutex<'d, TxStateInner<'d, T>>, + phantom: PhantomData<&'d mut T>, } -impl<'d, T: Instance> Unpin for BufferedUart<'d, T> {} -impl<'d, T: Instance> Unpin for BufferedUartRx<'d, T> {} -impl<'d, T: Instance> Unpin for BufferedUartTx<'d, T> {} +fn init<'d, T: Instance + 'd>( + irq: PeripheralRef<'d, T::Interrupt>, + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, +) { + super::Uart::<'d, T, Async>::init(tx, rx, rts, cts, config); + + let state = T::state(); + let len = tx_buffer.len(); + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + let len = rx_buffer.len(); + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; + + // From the datasheet: + // "The transmit interrupt is based on a transition through a level, rather + // than on the level itself. When the interrupt and the UART is enabled + // before any data is written to the transmit FIFO the interrupt is not set. + // The interrupt is only set, after written data leaves the single location + // of the transmit FIFO and it becomes empty." + // + // This means we can leave the interrupt enabled the whole time as long as + // we clear it after it happens. The downside is that the we manually have + // to pend the ISR when we want data transmission to start. + let regs = T::regs(); + unsafe { regs.uartimsc().write_set(|w| w.set_txim(true)) }; + + irq.set_handler(on_interrupt::); + irq.unpend(); + irq.enable(); +} impl<'d, T: Instance> BufferedUart<'d, T> { - pub fn new( - state: &'d mut State<'d, T>, - _uart: Uart<'d, T, M>, + pub fn new( + _uart: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], - ) -> BufferedUart<'d, T> { - into_ref!(irq); - - let r = T::regs(); - unsafe { - r.uartimsc().modify(|w| { - w.set_rxim(true); - w.set_rtim(true); - w.set_txim(true); - }); - } - + config: Config, + ) -> Self { + into_ref!(irq, tx, rx); + init::( + irq, + Some(tx.map_into()), + Some(rx.map_into()), + None, + None, + tx_buffer, + rx_buffer, + config, + ); Self { - inner: PeripheralMutex::new(irq, &mut state.0, move || FullStateInner { - tx: TxStateInner { - phantom: PhantomData, - waker: WakerRegistration::new(), - buf: RingBuffer::new(tx_buffer), - }, - rx: RxStateInner { - phantom: PhantomData, - waker: WakerRegistration::new(), - buf: RingBuffer::new(rx_buffer), - }, - }), + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, } } + + pub fn new_with_rtscts( + _uart: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(irq, tx, rx, cts, rts); + init::( + irq, + Some(tx.map_into()), + Some(rx.map_into()), + Some(rts.map_into()), + Some(cts.map_into()), + tx_buffer, + rx_buffer, + config, + ); + Self { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } + + pub fn split(self) -> (BufferedUartRx<'d, T>, BufferedUartTx<'d, T>) { + (self.rx, self.tx) + } } impl<'d, T: Instance> BufferedUartRx<'d, T> { - pub fn new( - state: &'d mut RxState<'d, T>, - _uart: UartRx<'d, T, M>, + pub fn new( + _uart: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, rx_buffer: &'d mut [u8], - ) -> BufferedUartRx<'d, T> { - into_ref!(irq); + config: Config, + ) -> Self { + into_ref!(irq, rx); + init::(irq, None, Some(rx.map_into()), None, None, &mut [], rx_buffer, config); + Self { phantom: PhantomData } + } - let r = T::regs(); + pub fn new_with_rts( + _uart: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(irq, rx, rts); + init::( + irq, + None, + Some(rx.map_into()), + Some(rts.map_into()), + None, + &mut [], + rx_buffer, + config, + ); + Self { phantom: PhantomData } + } + + fn read<'a>(buf: &'a mut [u8]) -> impl Future> + 'a { + poll_fn(move |cx| { + let state = T::state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let n = rx_reader.pop(|data| { + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + n + }); + if n == 0 { + state.rx_waker.register(cx.waker()); + return Poll::Pending; + } + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full. + let regs = T::regs(); + unsafe { + regs.uartimsc().write_set(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + } + + Poll::Ready(Ok(n)) + }) + } + + fn fill_buf<'a>() -> impl Future> { + poll_fn(move |cx| { + let state = T::state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let (p, n) = rx_reader.pop_buf(); + if n == 0 { + state.rx_waker.register(cx.waker()); + return Poll::Pending; + } + + let buf = unsafe { slice::from_raw_parts(p, n) }; + Poll::Ready(Ok(buf)) + }) + } + + fn consume(amt: usize) { + let state = T::state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + rx_reader.pop_done(amt); + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full. + let regs = T::regs(); unsafe { - r.uartimsc().modify(|w| { + regs.uartimsc().write_set(|w| { w.set_rxim(true); w.set_rtim(true); }); } - - Self { - inner: PeripheralMutex::new(irq, &mut state.0, move || RxStateInner { - phantom: PhantomData, - - buf: RingBuffer::new(rx_buffer), - waker: WakerRegistration::new(), - }), - } } } impl<'d, T: Instance> BufferedUartTx<'d, T> { - pub fn new( - state: &'d mut TxState<'d, T>, - _uart: UartTx<'d, T, M>, + pub fn new( + _uart: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, tx_buffer: &'d mut [u8], - ) -> BufferedUartTx<'d, T> { - into_ref!(irq); + config: Config, + ) -> Self { + into_ref!(irq, tx); + init::(irq, Some(tx.map_into()), None, None, None, tx_buffer, &mut [], config); + Self { phantom: PhantomData } + } - let r = T::regs(); + pub fn new_with_cts( + _uart: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(irq, tx, cts); + init::( + irq, + Some(tx.map_into()), + None, + None, + Some(cts.map_into()), + tx_buffer, + &mut [], + config, + ); + Self { phantom: PhantomData } + } + + fn write<'a>(buf: &'a [u8]) -> impl Future> + 'a { + poll_fn(move |cx| { + let state = T::state(); + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + n + }); + if n == 0 { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + // The TX interrupt only triggers when the there was data in the + // FIFO and the number of bytes drops below a threshold. When the + // FIFO was empty we have to manually pend the interrupt to shovel + // TX data from the buffer into the FIFO. + unsafe { T::Interrupt::steal() }.pend(); + Poll::Ready(Ok(n)) + }) + } + + fn flush() -> impl Future> { + poll_fn(move |cx| { + let state = T::state(); + if !state.tx_buf.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + } +} + +impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> { + fn drop(&mut self) { + let state = T::state(); unsafe { - r.uartimsc().modify(|w| { - w.set_txim(true); + state.rx_buf.deinit(); + + // TX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.tx_buf.len() == 0 { + T::Interrupt::steal().disable(); + } + } + } +} + +impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { + fn drop(&mut self) { + let state = T::state(); + unsafe { + state.tx_buf.deinit(); + + // RX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.rx_buf.len() == 0 { + T::Interrupt::steal().disable(); + } + } + } +} + +pub(crate) unsafe fn on_interrupt(_: *mut ()) { + let r = T::regs(); + let s = T::state(); + + unsafe { + // Clear TX and error interrupt flags + // RX interrupt flags are cleared by reading from the FIFO. + let ris = r.uartris().read(); + r.uarticr().write(|w| { + w.set_txic(ris.txris()); + w.set_feic(ris.feris()); + w.set_peic(ris.peris()); + w.set_beic(ris.beris()); + w.set_oeic(ris.oeris()); + }); + + trace!("on_interrupt ris={:#X}", ris.0); + + // Errors + if ris.feris() { + warn!("Framing error"); + } + if ris.peris() { + warn!("Parity error"); + } + if ris.beris() { + warn!("Break error"); + } + if ris.oeris() { + warn!("Overrun error"); + } + + // RX + let mut rx_writer = s.rx_buf.writer(); + let rx_buf = rx_writer.push_slice(); + let mut n_read = 0; + for rx_byte in rx_buf { + if r.uartfr().read().rxfe() { + break; + } + *rx_byte = r.uartdr().read().data(); + n_read += 1; + } + if n_read > 0 { + rx_writer.push_done(n_read); + s.rx_waker.wake(); + } + // Disable any further RX interrupts when the buffer becomes full. + if s.rx_buf.is_full() { + r.uartimsc().write_clear(|w| { + w.set_rxim(true); + w.set_rtim(true); }); } - Self { - inner: PeripheralMutex::new(irq, &mut state.0, move || TxStateInner { - phantom: PhantomData, - - buf: RingBuffer::new(tx_buffer), - waker: WakerRegistration::new(), - }), - } - } -} - -impl<'d, T: Instance> PeripheralState for FullStateInner<'d, T> -where - Self: 'd, -{ - type Interrupt = T::Interrupt; - fn on_interrupt(&mut self) { - self.rx.on_interrupt(); - self.tx.on_interrupt(); - } -} - -impl<'d, T: Instance> RxStateInner<'d, T> -where - Self: 'd, -{ - fn read(&mut self, buf: &mut [u8], waker: &Waker) -> (Poll>, bool) { - // We have data ready in buffer? Return it. - let mut do_pend = false; - let data = self.buf.pop_buf(); - if !data.is_empty() { - let len = data.len().min(buf.len()); - buf[..len].copy_from_slice(&data[..len]); - - if self.buf.is_full() { - do_pend = true; + // TX + let mut tx_reader = s.tx_buf.reader(); + let tx_buf = tx_reader.pop_slice(); + let mut n_written = 0; + for tx_byte in tx_buf.iter_mut() { + if r.uartfr().read().txff() { + break; } - self.buf.pop(len); - - return (Poll::Ready(Ok(len)), do_pend); + r.uartdr().write(|w| w.set_data(*tx_byte)); + n_written += 1; } - - self.waker.register(waker); - (Poll::Pending, do_pend) - } - - fn fill_buf<'a>(&mut self, waker: &Waker) -> Poll> { - // We have data ready in buffer? Return it. - let buf = self.buf.pop_buf(); - if !buf.is_empty() { - let buf: &[u8] = buf; - // Safety: buffer lives as long as uart - let buf: &[u8] = unsafe { core::mem::transmute(buf) }; - return Poll::Ready(Ok(buf)); - } - - self.waker.register(waker); - Poll::Pending - } - - fn consume(&mut self, amt: usize) -> bool { - let full = self.buf.is_full(); - self.buf.pop(amt); - full - } -} - -impl<'d, T: Instance> PeripheralState for RxStateInner<'d, T> -where - Self: 'd, -{ - type Interrupt = T::Interrupt; - fn on_interrupt(&mut self) { - let r = T::regs(); - unsafe { - let ris = r.uartris().read(); - // Clear interrupt flags - r.uarticr().modify(|w| { - w.set_rxic(true); - w.set_rtic(true); - }); - - if ris.peris() { - warn!("Parity error"); - r.uarticr().modify(|w| { - w.set_peic(true); - }); - } - if ris.feris() { - warn!("Framing error"); - r.uarticr().modify(|w| { - w.set_feic(true); - }); - } - if ris.beris() { - warn!("Break error"); - r.uarticr().modify(|w| { - w.set_beic(true); - }); - } - if ris.oeris() { - warn!("Overrun error"); - r.uarticr().modify(|w| { - w.set_oeic(true); - }); - } - - if !r.uartfr().read().rxfe() { - let buf = self.buf.push_buf(); - if !buf.is_empty() { - buf[0] = r.uartdr().read().data(); - self.buf.push(1); - } else { - warn!("RX buffer full, discard received byte"); - } - - if self.buf.is_full() { - self.waker.wake(); - } - } - - if ris.rtris() { - self.waker.wake(); - }; - } - } -} - -impl<'d, T: Instance> TxStateInner<'d, T> -where - Self: 'd, -{ - fn write(&mut self, buf: &[u8], waker: &Waker) -> (Poll>, bool) { - let empty = self.buf.is_empty(); - let tx_buf = self.buf.push_buf(); - if tx_buf.is_empty() { - self.waker.register(waker); - return (Poll::Pending, empty); - } - - let n = core::cmp::min(tx_buf.len(), buf.len()); - tx_buf[..n].copy_from_slice(&buf[..n]); - self.buf.push(n); - - (Poll::Ready(Ok(n)), empty) - } - - fn flush(&mut self, waker: &Waker) -> Poll> { - if !self.buf.is_empty() { - self.waker.register(waker); - return Poll::Pending; - } - - Poll::Ready(Ok(())) - } -} - -impl<'d, T: Instance> PeripheralState for TxStateInner<'d, T> -where - Self: 'd, -{ - type Interrupt = T::Interrupt; - fn on_interrupt(&mut self) { - let r = T::regs(); - unsafe { - let buf = self.buf.pop_buf(); - if !buf.is_empty() { - r.uartimsc().modify(|w| { - w.set_txim(true); - }); - r.uartdr().write(|w| w.set_data(buf[0].into())); - self.buf.pop(1); - self.waker.wake(); - } else { - // Disable interrupt until we have something to transmit again - r.uartimsc().modify(|w| { - w.set_txim(false); - }); - } + if n_written > 0 { + tx_reader.pop_done(n_written); + s.tx_waker.wake(); } + // The TX interrupt only triggers once when the FIFO threshold is + // crossed. No need to disable it when the buffer becomes empty + // as it does re-trigger anymore once we have cleared it. } } @@ -355,135 +427,53 @@ impl<'d, T: Instance> embedded_io::Io for BufferedUartTx<'d, T> { } impl<'d, T: Instance + 'd> embedded_io::asynch::Read for BufferedUart<'d, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - poll_fn(move |cx| { - let (res, do_pend) = self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - state.rx.read(buf, cx.waker()) - }); - - if do_pend { - self.inner.pend(); - } - - res - }) + async fn read(&mut self, buf: &mut [u8]) -> Result { + BufferedUartRx::<'d, T>::read(buf).await } } impl<'d, T: Instance + 'd> embedded_io::asynch::Read for BufferedUartRx<'d, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - poll_fn(move |cx| { - let (res, do_pend) = self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - state.read(buf, cx.waker()) - }); - - if do_pend { - self.inner.pend(); - } - - res - }) + async fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(buf).await } } impl<'d, T: Instance + 'd> embedded_io::asynch::BufRead for BufferedUart<'d, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; - - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - poll_fn(move |cx| { - self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - state.rx.fill_buf(cx.waker()) - }) - }) + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + BufferedUartRx::<'d, T>::fill_buf().await } fn consume(&mut self, amt: usize) { - let signal = self.inner.with(|state| state.rx.consume(amt)); - if signal { - self.inner.pend(); - } + BufferedUartRx::<'d, T>::consume(amt) } } impl<'d, T: Instance + 'd> embedded_io::asynch::BufRead for BufferedUartRx<'d, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; - - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - poll_fn(move |cx| { - self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - state.fill_buf(cx.waker()) - }) - }) + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Self::fill_buf().await } fn consume(&mut self, amt: usize) { - let signal = self.inner.with(|state| state.consume(amt)); - if signal { - self.inner.pend(); - } + Self::consume(amt) } } impl<'d, T: Instance + 'd> embedded_io::asynch::Write for BufferedUart<'d, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - poll_fn(move |cx| { - let (poll, empty) = self.inner.with(|state| state.tx.write(buf, cx.waker())); - if empty { - self.inner.pend(); - } - poll - }) + async fn write(&mut self, buf: &[u8]) -> Result { + BufferedUartTx::<'d, T>::write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - poll_fn(move |cx| self.inner.with(|state| state.tx.flush(cx.waker()))) + async fn flush(&mut self) -> Result<(), Self::Error> { + BufferedUartTx::<'d, T>::flush().await } } impl<'d, T: Instance + 'd> embedded_io::asynch::Write for BufferedUartTx<'d, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - poll_fn(move |cx| { - let (poll, empty) = self.inner.with(|state| state.write(buf, cx.waker())); - if empty { - self.inner.pend(); - } - poll - }) + async fn write(&mut self, buf: &[u8]) -> Result { + Self::write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - poll_fn(move |cx| self.inner.with(|state| state.flush(cx.waker()))) + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush().await } } diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 56c25e189..bbbf97c01 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -7,6 +7,11 @@ use crate::gpio::sealed::Pin; use crate::gpio::AnyPin; use crate::{pac, peripherals, Peripheral}; +#[cfg(feature = "nightly")] +mod buffered; +#[cfg(feature = "nightly")] +pub use buffered::{BufferedUart, BufferedUartRx, BufferedUartTx}; + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum DataBits { DataBits5, @@ -93,7 +98,19 @@ pub struct UartRx<'d, T: Instance, M: Mode> { } impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { - fn new(tx_dma: Option>) -> Self { + /// Create a new DMA-enabled UART which can only send data + pub fn new( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx, tx_dma); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(Some(tx_dma.map_into())) + } + + fn new_inner(tx_dma: Option>) -> Self { Self { tx_dma, phantom: PhantomData, @@ -135,7 +152,19 @@ impl<'d, T: Instance> UartTx<'d, T, Async> { } impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { - fn new(rx_dma: Option>) -> Self { + /// Create a new DMA-enabled UART which can only send data + pub fn new( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(rx, rx_dma); + Uart::::init(Some(rx.map_into()), None, None, None, config); + Self::new_inner(Some(rx_dma.map_into())) + } + + fn new_inner(rx_dma: Option>) -> Self { Self { rx_dma, phantom: PhantomData, @@ -196,7 +225,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { config: Config, ) -> Self { into_ref!(tx, rx); - Self::new_inner(uart, rx.map_into(), tx.map_into(), None, None, None, None, config) + Self::new_inner(uart, tx.map_into(), rx.map_into(), None, None, None, None, config) } /// Create a new UART with hardware flow control (RTS/CTS) @@ -211,8 +240,8 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { into_ref!(tx, rx, cts, rts); Self::new_inner( uart, - rx.map_into(), tx.map_into(), + rx.map_into(), Some(rts.map_into()), Some(cts.map_into()), None, @@ -235,8 +264,8 @@ impl<'d, T: Instance> Uart<'d, T, Async> { into_ref!(tx, rx, tx_dma, rx_dma); Self::new_inner( uart, - rx.map_into(), tx.map_into(), + rx.map_into(), None, None, Some(tx_dma.map_into()), @@ -259,8 +288,8 @@ impl<'d, T: Instance> Uart<'d, T, Async> { into_ref!(tx, rx, cts, rts, tx_dma, rx_dma); Self::new_inner( uart, - rx.map_into(), tx.map_into(), + rx.map_into(), Some(rts.map_into()), Some(cts.map_into()), Some(tx_dma.map_into()), @@ -273,41 +302,52 @@ impl<'d, T: Instance> Uart<'d, T, Async> { impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { fn new_inner( _uart: impl Peripheral

+ 'd, - tx: PeripheralRef<'d, AnyPin>, - rx: PeripheralRef<'d, AnyPin>, - rts: Option>, - cts: Option>, + mut tx: PeripheralRef<'d, AnyPin>, + mut rx: PeripheralRef<'d, AnyPin>, + mut rts: Option>, + mut cts: Option>, tx_dma: Option>, rx_dma: Option>, config: Config, ) -> Self { - into_ref!(_uart); + Self::init( + Some(tx.reborrow()), + Some(rx.reborrow()), + rts.as_mut().map(|x| x.reborrow()), + cts.as_mut().map(|x| x.reborrow()), + config, + ); + Self { + tx: UartTx::new_inner(tx_dma), + rx: UartRx::new_inner(rx_dma), + } + } + + fn init( + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) { + let r = T::regs(); unsafe { - let r = T::regs(); - - tx.io().ctrl().write(|w| w.set_funcsel(2)); - rx.io().ctrl().write(|w| w.set_funcsel(2)); - - tx.pad_ctrl().write(|w| { - w.set_ie(true); - }); - - rx.pad_ctrl().write(|w| { - w.set_ie(true); - }); - + if let Some(pin) = &tx { + pin.io().ctrl().write(|w| w.set_funcsel(2)); + pin.pad_ctrl().write(|w| w.set_ie(true)); + } + if let Some(pin) = &rx { + pin.io().ctrl().write(|w| w.set_funcsel(2)); + pin.pad_ctrl().write(|w| w.set_ie(true)); + } if let Some(pin) = &cts { pin.io().ctrl().write(|w| w.set_funcsel(2)); - pin.pad_ctrl().write(|w| { - w.set_ie(true); - }); + pin.pad_ctrl().write(|w| w.set_ie(true)); } if let Some(pin) = &rts { pin.io().ctrl().write(|w| w.set_funcsel(2)); - pin.pad_ctrl().write(|w| { - w.set_ie(true); - }); + pin.pad_ctrl().write(|w| w.set_ie(true)); } let clk_base = crate::clocks::clk_peri_freq(); @@ -359,11 +399,6 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { w.set_rtsen(rts.is_some()); }); } - - Self { - tx: UartTx::new(tx_dma), - rx: UartRx::new(rx_dma), - } } } @@ -611,11 +646,6 @@ mod eha { } } -#[cfg(feature = "nightly")] -mod buffered; -#[cfg(feature = "nightly")] -pub use buffered::*; - mod sealed { use super::*; @@ -628,6 +658,9 @@ mod sealed { type Interrupt: crate::interrupt::Interrupt; fn regs() -> pac::uart::Uart; + + #[cfg(feature = "nightly")] + fn state() -> &'static buffered::State; } pub trait TxPin {} pub trait RxPin {} @@ -663,6 +696,12 @@ macro_rules! impl_instance { fn regs() -> pac::uart::Uart { pac::$inst } + + #[cfg(feature = "nightly")] + fn state() -> &'static buffered::State { + static STATE: buffered::State = buffered::State::new(); + &STATE + } } impl Instance for peripherals::$inst {} }; diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 0a904aab3..2e3708eff 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -1,10 +1,9 @@ -use core::future::{poll_fn, Future}; +use core::future::poll_fn; use core::marker::PhantomData; use core::slice; -use core::sync::atomic::Ordering; +use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; -use atomic_polyfill::compiler_fence; use embassy_hal_common::into_ref; use embassy_sync::waitqueue::AtomicWaker; use embassy_usb_driver as driver; @@ -195,13 +194,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, driver::EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -220,14 +219,16 @@ impl<'d, T: Instance> Driver<'d, T> { let (index, ep) = index.ok_or(EndpointAllocError)?; assert!(!ep.used); - if max_packet_size > 64 { + // as per datasheet, the maximum buffer size is 64, except for isochronous + // endpoints, which are allowed to be up to 1023 bytes. + if (ep_type != EndpointType::Isochronous && max_packet_size > 64) || max_packet_size > 1023 { warn!("max_packet_size too high: {}", max_packet_size); return Err(EndpointAllocError); } // ep mem addrs must be 64-byte aligned, so there's no point in trying // to allocate smaller chunks to save memory. - let len = 64; + let len = (max_packet_size + 63) / 64 * 64; let addr = self.ep_mem_free; if addr + len > EP_MEMORY_SIZE as _ { @@ -280,7 +281,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, buf, }) @@ -297,18 +298,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { @@ -352,9 +353,7 @@ pub struct Bus<'d, T: Instance> { } impl<'d, T: Instance> driver::Bus for Bus<'d, T> { - type PollFuture<'a> = impl Future + 'a where Self: 'a; - - fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { + async fn poll(&mut self) -> Event { poll_fn(move |cx| unsafe { BUS_WAKER.register(cx.waker()); @@ -406,13 +405,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { }); Poll::Pending }) - } - - #[inline] - fn set_address(&mut self, addr: u8) { - let regs = T::regs(); - trace!("setting addr: {}", addr); - unsafe { regs.addr_endp().write(|w| w.set_address(addr)) } + .await } fn endpoint_set_stalled(&mut self, _ep_addr: EndpointAddress, _stalled: bool) { @@ -456,22 +449,12 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { } } - type EnableFuture<'a> = impl Future + 'a where Self: 'a; + async fn enable(&mut self) {} - fn enable(&mut self) -> Self::EnableFuture<'_> { - async move {} - } + async fn disable(&mut self) {} - type DisableFuture<'a> = impl Future + 'a where Self: 'a; - - fn disable(&mut self) -> Self::DisableFuture<'_> { - async move {} - } - - type RemoteWakeupFuture<'a> = impl Future> + 'a where Self: 'a; - - fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> { - async move { Err(Unsupported) } + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) } } @@ -515,24 +498,20 @@ impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { - async move { - trace!("wait_enabled IN WAITING"); - let index = self.info.addr.index(); - poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let val = unsafe { T::dpram().ep_in_control(self.info.addr.index() - 1).read() }; - if val.enable() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - trace!("wait_enabled IN OK"); - } + async fn wait_enabled(&mut self) { + trace!("wait_enabled IN WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + let val = unsafe { T::dpram().ep_in_control(self.info.addr.index() - 1).read() }; + if val.enable() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("wait_enabled IN OK"); } } @@ -541,117 +520,105 @@ impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { - async move { - trace!("wait_enabled OUT WAITING"); - let index = self.info.addr.index(); - poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let val = unsafe { T::dpram().ep_out_control(self.info.addr.index() - 1).read() }; - if val.enable() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - trace!("wait_enabled OUT OK"); - } + async fn wait_enabled(&mut self) { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = unsafe { T::dpram().ep_out_control(self.info.addr.index() - 1).read() }; + if val.enable() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("wait_enabled OUT OK"); } } impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - trace!("READ WAITING, buf.len() = {}", buf.len()); - let index = self.info.addr.index(); - let val = poll_fn(|cx| unsafe { - EP_OUT_WAKERS[index].register(cx.waker()); - let val = T::dpram().ep_out_buffer_control(index).read(); - if val.available(0) { - Poll::Pending - } else { - Poll::Ready(val) - } - }) - .await; - - let rx_len = val.length(0) as usize; - if rx_len > buf.len() { - return Err(EndpointError::BufferOverflow); + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("READ WAITING, buf.len() = {}", buf.len()); + let index = self.info.addr.index(); + let val = poll_fn(|cx| unsafe { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) } - self.buf.read(&mut buf[..rx_len]); + }) + .await; - trace!("READ OK, rx_len = {}", rx_len); - - unsafe { - let pid = !val.pid(0); - T::dpram().ep_out_buffer_control(index).write(|w| { - w.set_pid(0, pid); - w.set_length(0, self.info.max_packet_size); - }); - cortex_m::asm::delay(12); - T::dpram().ep_out_buffer_control(index).write(|w| { - w.set_pid(0, pid); - w.set_length(0, self.info.max_packet_size); - w.set_available(0, true); - }); - } - - Ok(rx_len) + let rx_len = val.length(0) as usize; + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); } + self.buf.read(&mut buf[..rx_len]); + + trace!("READ OK, rx_len = {}", rx_len); + + unsafe { + let pid = !val.pid(0); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + w.set_available(0, true); + }); + } + + Ok(rx_len) } } impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - if buf.len() > self.info.max_packet_size as usize { - return Err(EndpointError::BufferOverflow); - } - - trace!("WRITE WAITING"); - - let index = self.info.addr.index(); - let val = poll_fn(|cx| unsafe { - EP_IN_WAKERS[index].register(cx.waker()); - let val = T::dpram().ep_in_buffer_control(index).read(); - if val.available(0) { - Poll::Pending - } else { - Poll::Ready(val) - } - }) - .await; - - self.buf.write(buf); - - unsafe { - let pid = !val.pid(0); - T::dpram().ep_in_buffer_control(index).write(|w| { - w.set_pid(0, pid); - w.set_length(0, buf.len() as _); - w.set_full(0, true); - }); - cortex_m::asm::delay(12); - T::dpram().ep_in_buffer_control(index).write(|w| { - w.set_pid(0, pid); - w.set_length(0, buf.len() as _); - w.set_full(0, true); - w.set_available(0, true); - }); - } - - trace!("WRITE OK"); - - Ok(()) + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); } + + trace!("WRITE WAITING"); + + let index = self.info.addr.index(); + let val = poll_fn(|cx| unsafe { + EP_IN_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_in_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + self.buf.write(buf); + + unsafe { + let pid = !val.pid(0); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + w.set_available(0, true); + }); + } + + trace!("WRITE OK"); + + Ok(()) } } @@ -661,187 +628,191 @@ pub struct ControlPipe<'d, T: Instance> { } impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { - type SetupFuture<'a> = impl Future + 'a where Self: 'a; - type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; - type DataInFuture<'a> = impl Future> + 'a where Self: 'a; - type AcceptFuture<'a> = impl Future + 'a where Self: 'a; - type RejectFuture<'a> = impl Future + 'a where Self: 'a; - fn max_packet_size(&self) -> usize { 64 } - fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { - async move { - loop { - trace!("SETUP read waiting"); - let regs = T::regs(); - unsafe { regs.inte().write_set(|w| w.set_setup_req(true)) }; - - poll_fn(|cx| unsafe { - EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if regs.sie_status().read().setup_rec() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - let mut buf = [0; 8]; - EndpointBuffer::::new(0, 8).read(&mut buf); - - let regs = T::regs(); - unsafe { - regs.sie_status().write(|w| w.set_setup_rec(true)); - - // set PID to 0, so (after toggling) first DATA is PID 1 - T::dpram().ep_in_buffer_control(0).write(|w| w.set_pid(0, false)); - T::dpram().ep_out_buffer_control(0).write(|w| w.set_pid(0, false)); - } - - trace!("SETUP read ok"); - return buf; - } - } - } - - fn data_out<'a>(&'a mut self, buf: &'a mut [u8], _first: bool, _last: bool) -> Self::DataOutFuture<'a> { - async move { - unsafe { - let bufcontrol = T::dpram().ep_out_buffer_control(0); - let pid = !bufcontrol.read().pid(0); - bufcontrol.write(|w| { - w.set_length(0, self.max_packet_size); - w.set_pid(0, pid); - }); - cortex_m::asm::delay(12); - bufcontrol.write(|w| { - w.set_length(0, self.max_packet_size); - w.set_pid(0, pid); - w.set_available(0, true); - }); - } - - trace!("control: data_out len={} first={} last={}", buf.len(), _first, _last); - let val = poll_fn(|cx| unsafe { - EP_OUT_WAKERS[0].register(cx.waker()); - let val = T::dpram().ep_out_buffer_control(0).read(); - if val.available(0) { - Poll::Pending - } else { - Poll::Ready(val) - } - }) - .await; - - let rx_len = val.length(0) as _; - trace!("control data_out DONE, rx_len = {}", rx_len); - - if rx_len > buf.len() { - return Err(EndpointError::BufferOverflow); - } - EndpointBuffer::::new(0x100, 64).read(&mut buf[..rx_len]); - - Ok(rx_len) - } - } - - fn data_in<'a>(&'a mut self, buf: &'a [u8], _first: bool, _last: bool) -> Self::DataInFuture<'a> { - async move { - trace!("control: data_in len={} first={} last={}", buf.len(), _first, _last); - - if buf.len() > 64 { - return Err(EndpointError::BufferOverflow); - } - EndpointBuffer::::new(0x100, 64).write(buf); - - unsafe { - let bufcontrol = T::dpram().ep_in_buffer_control(0); - let pid = !bufcontrol.read().pid(0); - bufcontrol.write(|w| { - w.set_length(0, buf.len() as _); - w.set_pid(0, pid); - w.set_full(0, true); - }); - cortex_m::asm::delay(12); - bufcontrol.write(|w| { - w.set_length(0, buf.len() as _); - w.set_pid(0, pid); - w.set_full(0, true); - w.set_available(0, true); - }); - } + async fn setup(&mut self) -> [u8; 8] { + loop { + trace!("SETUP read waiting"); + let regs = T::regs(); + unsafe { regs.inte().write_set(|w| w.set_setup_req(true)) }; poll_fn(|cx| unsafe { - EP_IN_WAKERS[0].register(cx.waker()); - let bufcontrol = T::dpram().ep_in_buffer_control(0); - if bufcontrol.read().available(0) { - Poll::Pending - } else { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.sie_status().read().setup_rec() { Poll::Ready(()) + } else { + Poll::Pending } }) .await; - trace!("control: data_in DONE"); - if _last { - // prepare status phase right away. - unsafe { - let bufcontrol = T::dpram().ep_out_buffer_control(0); - bufcontrol.write(|w| { - w.set_length(0, 0); - w.set_pid(0, true); - }); - cortex_m::asm::delay(12); - bufcontrol.write(|w| { - w.set_length(0, 0); - w.set_pid(0, true); - w.set_available(0, true); - }); - } - } - - Ok(()) - } - } - - fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a> { - async move { - trace!("control: accept"); - - unsafe { - let bufcontrol = T::dpram().ep_in_buffer_control(0); - bufcontrol.write(|w| { - w.set_length(0, 0); - w.set_pid(0, true); - w.set_full(0, true); - }); - cortex_m::asm::delay(12); - bufcontrol.write(|w| { - w.set_length(0, 0); - w.set_pid(0, true); - w.set_full(0, true); - w.set_available(0, true); - }); - } - } - } - - fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a> { - async move { - trace!("control: reject"); + let mut buf = [0; 8]; + EndpointBuffer::::new(0, 8).read(&mut buf); let regs = T::regs(); unsafe { - regs.ep_stall_arm().write_set(|w| { - w.set_ep0_in(true); - w.set_ep0_out(true); - }); - T::dpram().ep_out_buffer_control(0).write(|w| w.set_stall(true)); - T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true)); + regs.sie_status().write(|w| w.set_setup_rec(true)); + + // set PID to 0, so (after toggling) first DATA is PID 1 + T::dpram().ep_in_buffer_control(0).write(|w| w.set_pid(0, false)); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_pid(0, false)); } + + trace!("SETUP read ok"); + return buf; } } + + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result { + unsafe { + let bufcontrol = T::dpram().ep_out_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + w.set_available(0, true); + }); + } + + trace!("control: data_out len={} first={} last={}", buf.len(), first, last); + let val = poll_fn(|cx| unsafe { + EP_OUT_WAKERS[0].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(0).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + let rx_len = val.length(0) as _; + trace!("control data_out DONE, rx_len = {}", rx_len); + + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).read(&mut buf[..rx_len]); + + Ok(rx_len) + } + + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in len={} first={} last={}", data.len(), first, last); + + if data.len() > 64 { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).write(data); + + unsafe { + let bufcontrol = T::dpram().ep_in_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, data.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, data.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + w.set_available(0, true); + }); + } + + poll_fn(|cx| unsafe { + EP_IN_WAKERS[0].register(cx.waker()); + let bufcontrol = T::dpram().ep_in_buffer_control(0); + if bufcontrol.read().available(0) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("control: data_in DONE"); + + if last { + // prepare status phase right away. + unsafe { + let bufcontrol = T::dpram().ep_out_buffer_control(0); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_available(0, true); + }); + } + } + + Ok(()) + } + + async fn accept(&mut self) { + trace!("control: accept"); + + let bufcontrol = T::dpram().ep_in_buffer_control(0); + unsafe { + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + w.set_available(0, true); + }); + } + + // wait for completion before returning, needed so + // set_address() doesn't happen early. + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + if unsafe { bufcontrol.read().available(0) } { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } + + async fn reject(&mut self) { + trace!("control: reject"); + + let regs = T::regs(); + unsafe { + regs.ep_stall_arm().write_set(|w| { + w.set_ep0_in(true); + w.set_ep0_out(true); + }); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_stall(true)); + T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true)); + } + } + + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; + + let regs = T::regs(); + trace!("setting addr: {}", addr); + unsafe { regs.addr_endp().write(|w| w.set_address(addr)) } + } } diff --git a/embassy-rp/src/watchdog.rs b/embassy-rp/src/watchdog.rs new file mode 100644 index 000000000..f4f165b29 --- /dev/null +++ b/embassy-rp/src/watchdog.rs @@ -0,0 +1,111 @@ +//! Watchdog +//! +//! The watchdog is a countdown timer that can restart parts of the chip if it reaches zero. This can be used to restart the +//! processor if software gets stuck in an infinite loop. The programmer must periodically write a value to the watchdog to +//! stop it from reaching zero. +//! +//! Credit: based on `rp-hal` implementation (also licensed Apache+MIT) + +use core::marker::PhantomData; + +use embassy_time::Duration; + +use crate::pac; +use crate::peripherals::WATCHDOG; + +/// Watchdog peripheral +pub struct Watchdog { + phantom: PhantomData, + load_value: u32, // decremented by 2 per tick (µs) +} + +impl Watchdog { + /// Create a new `Watchdog` + pub fn new(_watchdog: WATCHDOG) -> Self { + Self { + phantom: PhantomData, + load_value: 0, + } + } + + /// Start tick generation on clk_tick which is driven from clk_ref. + /// + /// # Arguments + /// + /// * `cycles` - Total number of tick cycles before the next tick is generated. + /// It is expected to be the frequency in MHz of clk_ref. + pub fn enable_tick_generation(&mut self, cycles: u8) { + unsafe { + let watchdog = pac::WATCHDOG; + watchdog.tick().write(|w| { + w.set_enable(true); + w.set_cycles(cycles.into()) + }); + } + } + + /// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode + /// or when JTAG is accessing bus fabric + pub fn pause_on_debug(&mut self, pause: bool) { + unsafe { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| { + w.set_pause_dbg0(pause); + w.set_pause_dbg1(pause); + w.set_pause_jtag(pause); + }) + } + } + + fn load_counter(&self, counter: u32) { + unsafe { + let watchdog = pac::WATCHDOG; + watchdog.load().write_value(pac::watchdog::regs::Load(counter)); + } + } + + fn enable(&self, bit: bool) { + unsafe { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| w.set_enable(bit)) + } + } + + // Configure which hardware will be reset by the watchdog + // (everything except ROSC, XOSC) + unsafe fn configure_wdog_reset_triggers(&self) { + let psm = pac::PSM; + psm.wdsel().write_value(pac::psm::regs::Wdsel( + 0x0001ffff & !(0x01 << 0usize) & !(0x01 << 1usize), + )); + } + + /// Feed the watchdog timer + pub fn feed(&mut self) { + self.load_counter(self.load_value) + } + + /// Start the watchdog timer + pub fn start(&mut self, period: Duration) { + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.as_micros() as u32; + if delay_us > MAX_PERIOD / 2 { + panic!( + "Period cannot exceed maximum load value of {} ({} microseconds))", + MAX_PERIOD, + MAX_PERIOD / 2 + ); + } + // Due to a logic error, the watchdog decrements by 2 and + // the load value must be compensated; see RP2040-E1 + self.load_value = delay_us * 2; + + self.enable(false); + unsafe { + self.configure_wdog_reset_triggers(); + } + self.load_counter(self.load_value); + self.enable(true); + } +} diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 9050472c1..cd441325d 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -17,8 +17,9 @@ flavors = [ { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32f42.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, @@ -39,12 +40,12 @@ embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-4"]} embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } -embassy-net = { version = "0.1.0", path = "../embassy-net", optional = true } +embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9", optional = true} -embedded-hal-async = { version = "=0.1.0-alpha.2", optional = true} +embedded-hal-async = { version = "=0.2.0-alpha.0", optional = true} embedded-hal-nb = { version = "=1.0.0-alpha.1", optional = true} embedded-storage = "0.3.0" @@ -57,7 +58,7 @@ cortex-m = "0.7.6" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rand_core = "0.6.3" sdio-host = "0.5.0" -embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true } +embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "46d1b1c2ff13e31e282ec1e352421721694f126a", optional = true } critical-section = "1.1" atomic-polyfill = "1.0.1" stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] } @@ -67,7 +68,7 @@ nb = "1.0.0" stm32-fmc = "0.2.4" seq-macro = "0.3.0" cfg-if = "1.0.0" -embedded-io = { version = "0.3.0", features = ["async"], optional = true } +embedded-io = { version = "0.4.0", features = ["async"], optional = true } chrono = { version = "^0.4", default-features = false, optional = true} [build-dependencies] @@ -76,16 +77,17 @@ quote = "1.0.15" stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", default-features = false, features = ["metadata"]} [features] -defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-executor/defmt", "embassy-embedded-hal/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt"] -sdmmc-rs = ["embedded-sdmmc"] -net = ["embassy-net" ] +defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-executor/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt"] memory-x = ["stm32-metapac/memory-x"] subghz = [] exti = [] +# Enables additional driver features that depend on embassy-time +time = ["dep:embassy-time"] + # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. -_time-driver = ["dep:embassy-time"] +_time-driver = ["time"] time-driver-any = ["_time-driver"] time-driver-tim2 = ["_time-driver"] time-driver-tim3 = ["_time-driver"] @@ -109,6 +111,19 @@ unstable-traits = ["embedded-hal-1", "dep:embedded-hal-nb"] # BEGIN GENERATED FEATURES # Generated by stm32-gen-features. DO NOT EDIT. +stm32c011d6 = [ "stm32-metapac/stm32c011d6" ] +stm32c011f4 = [ "stm32-metapac/stm32c011f4" ] +stm32c011f6 = [ "stm32-metapac/stm32c011f6" ] +stm32c011j4 = [ "stm32-metapac/stm32c011j4" ] +stm32c011j6 = [ "stm32-metapac/stm32c011j6" ] +stm32c031c4 = [ "stm32-metapac/stm32c031c4" ] +stm32c031c6 = [ "stm32-metapac/stm32c031c6" ] +stm32c031f4 = [ "stm32-metapac/stm32c031f4" ] +stm32c031f6 = [ "stm32-metapac/stm32c031f6" ] +stm32c031g4 = [ "stm32-metapac/stm32c031g4" ] +stm32c031g6 = [ "stm32-metapac/stm32c031g6" ] +stm32c031k4 = [ "stm32-metapac/stm32c031k4" ] +stm32c031k6 = [ "stm32-metapac/stm32c031k6" ] stm32f030c6 = [ "stm32-metapac/stm32f030c6" ] stm32f030c8 = [ "stm32-metapac/stm32f030c8" ] stm32f030cc = [ "stm32-metapac/stm32f030cc" ] @@ -1317,11 +1332,9 @@ stm32u575zi = [ "stm32-metapac/stm32u575zi" ] stm32u585ai = [ "stm32-metapac/stm32u585ai" ] stm32u585ci = [ "stm32-metapac/stm32u585ci" ] stm32u585oi = [ "stm32-metapac/stm32u585oi" ] -stm32u585qe = [ "stm32-metapac/stm32u585qe" ] stm32u585qi = [ "stm32-metapac/stm32u585qi" ] stm32u585ri = [ "stm32-metapac/stm32u585ri" ] stm32u585vi = [ "stm32-metapac/stm32u585vi" ] -stm32u585ze = [ "stm32-metapac/stm32u585ze" ] stm32u585zi = [ "stm32-metapac/stm32u585zi" ] stm32wb10cc = [ "stm32-metapac/stm32wb10cc" ] stm32wb15cc = [ "stm32-metapac/stm32wb15cc" ] @@ -1339,7 +1352,6 @@ stm32wb55vc = [ "stm32-metapac/stm32wb55vc" ] stm32wb55ve = [ "stm32-metapac/stm32wb55ve" ] stm32wb55vg = [ "stm32-metapac/stm32wb55vg" ] stm32wb55vy = [ "stm32-metapac/stm32wb55vy" ] -stm32wb5mmg = [ "stm32-metapac/stm32wb5mmg" ] stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4" ] stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p" ] stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4" ] @@ -1348,8 +1360,6 @@ stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4" ] stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p" ] stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4" ] stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p" ] -stm32wl55uc-cm4 = [ "stm32-metapac/stm32wl55uc-cm4" ] -stm32wl55uc-cm0p = [ "stm32-metapac/stm32wl55uc-cm0p" ] stm32wle4c8 = [ "stm32-metapac/stm32wle4c8" ] stm32wle4cb = [ "stm32-metapac/stm32wle4cb" ] stm32wle4cc = [ "stm32-metapac/stm32wle4cc" ] @@ -1362,6 +1372,4 @@ stm32wle5cc = [ "stm32-metapac/stm32wle5cc" ] stm32wle5j8 = [ "stm32-metapac/stm32wle5j8" ] stm32wle5jb = [ "stm32-metapac/stm32wle5jb" ] stm32wle5jc = [ "stm32-metapac/stm32wle5jc" ] -stm32wle5u8 = [ "stm32-metapac/stm32wle5u8" ] -stm32wle5ub = [ "stm32-metapac/stm32wle5ub" ] # END GENERATED FEATURES diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index a4709f4ca..dbfc1370d 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -244,11 +244,13 @@ fn main() { (("usart", "CTS"), quote!(crate::usart::CtsPin)), (("usart", "RTS"), quote!(crate::usart::RtsPin)), (("usart", "CK"), quote!(crate::usart::CkPin)), + (("usart", "DE"), quote!(crate::usart::DePin)), (("lpuart", "TX"), quote!(crate::usart::TxPin)), (("lpuart", "RX"), quote!(crate::usart::RxPin)), (("lpuart", "CTS"), quote!(crate::usart::CtsPin)), (("lpuart", "RTS"), quote!(crate::usart::RtsPin)), (("lpuart", "CK"), quote!(crate::usart::CkPin)), + (("lpuart", "DE"), quote!(crate::usart::DePin)), (("spi", "SCK"), quote!(crate::spi::SckPin)), (("spi", "MOSI"), quote!(crate::spi::MosiPin)), (("spi", "MISO"), quote!(crate::spi::MisoPin)), @@ -275,22 +277,20 @@ fn main() { (("dcmi", "PIXCLK"), quote!(crate::dcmi::PixClkPin)), (("usb", "DP"), quote!(crate::usb::DpPin)), (("usb", "DM"), quote!(crate::usb::DmPin)), - (("otgfs", "DP"), quote!(crate::usb_otg::DpPin)), - (("otgfs", "DM"), quote!(crate::usb_otg::DmPin)), - (("otghs", "DP"), quote!(crate::usb_otg::DpPin)), - (("otghs", "DM"), quote!(crate::usb_otg::DmPin)), - (("otghs", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)), - (("otghs", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)), - (("otghs", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)), - (("otghs", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)), - (("otghs", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)), - (("otghs", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)), - (("otghs", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)), - (("otghs", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)), - (("otghs", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)), - (("otghs", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)), - (("otghs", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)), - (("otghs", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)), + (("otg", "DP"), quote!(crate::usb_otg::DpPin)), + (("otg", "DM"), quote!(crate::usb_otg::DmPin)), + (("otg", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)), + (("otg", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)), + (("otg", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)), + (("otg", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)), + (("otg", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)), + (("otg", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)), + (("otg", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)), + (("otg", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)), + (("otg", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)), + (("otg", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)), + (("otg", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)), + (("otg", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)), (("can", "TX"), quote!(crate::can::TxPin)), (("can", "RX"), quote!(crate::can::RxPin)), (("eth", "REF_CLK"), quote!(crate::eth::RefClkPin)), diff --git a/embassy-stm32/src/adc/f1.rs b/embassy-stm32/src/adc/f1.rs index 50d4f9bf9..d30ec001d 100644 --- a/embassy-stm32/src/adc/f1.rs +++ b/embassy-stm32/src/adc/f1.rs @@ -1,9 +1,7 @@ -use core::marker::PhantomData; - use embassy_hal_common::into_ref; use embedded_hal_02::blocking::delay::DelayUs; -use crate::adc::{AdcPin, Instance}; +use crate::adc::{Adc, AdcPin, Instance, SampleTime}; use crate::rcc::get_freqs; use crate::time::Hertz; use crate::Peripheral; @@ -29,70 +27,9 @@ impl super::sealed::AdcPin for Temperature { } } -mod sample_time { - /// ADC sample time - /// - /// The default setting is 1.5 ADC clock cycles. - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] - pub enum SampleTime { - /// 1.5 ADC clock cycles - Cycles1_5 = 0b000, - - /// 7.5 ADC clock cycles - Cycles7_5 = 0b001, - - /// 13.5 ADC clock cycles - Cycles13_5 = 0b010, - - /// 28.5 ADC clock cycles - Cycles28_5 = 0b011, - - /// 41.5 ADC clock cycles - Cycles41_5 = 0b100, - - /// 55.5 ADC clock cycles - Cycles55_5 = 0b101, - - /// 71.5 ADC clock cycles - Cycles71_5 = 0b110, - - /// 239.5 ADC clock cycles - Cycles239_5 = 0b111, - } - - impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::SampleTime { - match self { - SampleTime::Cycles1_5 => crate::pac::adc::vals::SampleTime::CYCLES1_5, - SampleTime::Cycles7_5 => crate::pac::adc::vals::SampleTime::CYCLES7_5, - SampleTime::Cycles13_5 => crate::pac::adc::vals::SampleTime::CYCLES13_5, - SampleTime::Cycles28_5 => crate::pac::adc::vals::SampleTime::CYCLES28_5, - SampleTime::Cycles41_5 => crate::pac::adc::vals::SampleTime::CYCLES41_5, - SampleTime::Cycles55_5 => crate::pac::adc::vals::SampleTime::CYCLES55_5, - SampleTime::Cycles71_5 => crate::pac::adc::vals::SampleTime::CYCLES71_5, - SampleTime::Cycles239_5 => crate::pac::adc::vals::SampleTime::CYCLES239_5, - } - } - } - - impl Default for SampleTime { - fn default() -> Self { - Self::Cycles28_5 - } - } -} - -pub use sample_time::SampleTime; - -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - calibrated_vdda: u32, - phantom: PhantomData<&'d mut T>, -} - impl<'d, T: Instance> Adc<'d, T> { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - into_ref!(_peri); + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + into_ref!(adc); T::enable(); T::reset(); unsafe { @@ -121,9 +58,8 @@ impl<'d, T: Instance> Adc<'d, T> { delay.delay_us((1_000_000) / Self::freq().0 + 1); Self { + adc, sample_time: Default::default(), - calibrated_vdda: VDDA_CALIB_MV, - phantom: PhantomData, } } @@ -162,29 +98,10 @@ impl<'d, T: Instance> Adc<'d, T> { Temperature {} } - /// Calculates the system VDDA by sampling the internal VREF channel and comparing - /// to the expected value. If the chip's VDDA is not stable, run this before each ADC - /// conversion. - pub fn calibrate(&mut self, vref: &mut Vref) -> u32 { - let old_sample_time = self.sample_time; - self.sample_time = SampleTime::Cycles239_5; - - let vref_samp = self.read(vref); - self.sample_time = old_sample_time; - - self.calibrated_vdda = (ADC_MAX * VREF_INT) / u32::from(vref_samp); - self.calibrated_vdda - } - pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; } - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.calibrated_vdda) / ADC_MAX) as u16 - } - /// Perform a single conversion. fn convert(&mut self) -> u16 { unsafe { @@ -222,14 +139,11 @@ impl<'d, T: Instance> Adc<'d, T> { } unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); if ch <= 9 { - T::regs() - .smpr2() - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time)); } else { - T::regs() - .smpr1() - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } } diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 0eb4eba73..ec49dace7 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -1,28 +1,38 @@ #![macro_use] -#[cfg_attr(adc_v4, path = "v4.rs")] -#[cfg_attr(adc_v3, path = "v3.rs")] -#[cfg_attr(adc_v2, path = "v2.rs")] -#[cfg_attr(adc_g0, path = "v3.rs")] #[cfg_attr(adc_f1, path = "f1.rs")] #[cfg_attr(adc_v1, path = "v1.rs")] +#[cfg_attr(adc_v2, path = "v2.rs")] +#[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")] +#[cfg_attr(adc_v4, path = "v4.rs")] mod _version; +#[cfg(not(any(adc_f1, adc_v1)))] +mod resolution; +#[cfg(not(adc_v1))] +mod sample_time; + #[allow(unused)] pub use _version::*; +#[cfg(not(any(adc_f1, adc_v1)))] +pub use resolution::Resolution; +#[cfg(not(adc_v1))] +pub use sample_time::SampleTime; use crate::peripherals; +#[cfg(not(adc_v1))] +pub struct Adc<'d, T: Instance> { + #[allow(unused)] + adc: crate::PeripheralRef<'d, T>, + sample_time: SampleTime, +} + pub(crate) mod sealed { pub trait Instance { - fn regs() -> &'static crate::pac::adc::Adc; + fn regs() -> crate::pac::adc::Adc; #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon; - } - - #[cfg(all(not(adc_f1), not(adc_v1)))] - pub trait Common { - fn regs() -> &'static crate::pac::adccommon::AdcCommon; + fn common_regs() -> crate::pac::adccommon::AdcCommon; } pub trait AdcPin { @@ -34,12 +44,11 @@ pub(crate) mod sealed { } } -#[cfg(not(any(adc_f1, adc_v2)))] -pub trait Instance: sealed::Instance + 'static {} -#[cfg(any(adc_f1, adc_v2))] -pub trait Instance: sealed::Instance + crate::rcc::RccPeripheral + 'static {} -#[cfg(all(not(adc_f1), not(adc_v1)))] -pub trait Common: sealed::Common + 'static {} +#[cfg(not(any(adc_f1, adc_v2, adc_v4)))] +pub trait Instance: sealed::Instance + crate::Peripheral

{} +#[cfg(any(adc_f1, adc_v2, adc_v4))] +pub trait Instance: sealed::Instance + crate::Peripheral

+ crate::rcc::RccPeripheral {} + pub trait AdcPin: sealed::AdcPin {} pub trait InternalChannel: sealed::InternalChannel {} @@ -47,14 +56,14 @@ pub trait InternalChannel: sealed::InternalChannel {} foreach_peripheral!( (adc, $inst:ident) => { impl crate::adc::sealed::Instance for peripherals::$inst { - fn regs() -> &'static crate::pac::adc::Adc { - &crate::pac::$inst + fn regs() -> crate::pac::adc::Adc { + crate::pac::$inst } #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon { + fn common_regs() -> crate::pac::adccommon::AdcCommon { foreach_peripheral!{ (adccommon, $common_inst:ident) => { - return &crate::pac::$common_inst + return crate::pac::$common_inst }; } } @@ -68,14 +77,14 @@ foreach_peripheral!( foreach_peripheral!( (adc, ADC3) => { impl crate::adc::sealed::Instance for peripherals::ADC3 { - fn regs() -> &'static crate::pac::adc::Adc { - &crate::pac::ADC3 + fn regs() -> crate::pac::adc::Adc { + crate::pac::ADC3 } #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon { + fn common_regs() -> crate::pac::adccommon::AdcCommon { foreach_peripheral!{ (adccommon, ADC3_COMMON) => { - return &crate::pac::ADC3_COMMON + return crate::pac::ADC3_COMMON }; } } @@ -85,14 +94,14 @@ foreach_peripheral!( }; (adc, $inst:ident) => { impl crate::adc::sealed::Instance for peripherals::$inst { - fn regs() -> &'static crate::pac::adc::Adc { - &crate::pac::$inst + fn regs() -> crate::pac::adc::Adc { + crate::pac::$inst } #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon { + fn common_regs() -> crate::pac::adccommon::AdcCommon { foreach_peripheral!{ (adccommon, ADC_COMMON) => { - return &crate::pac::ADC_COMMON + return crate::pac::ADC_COMMON }; } } @@ -102,19 +111,6 @@ foreach_peripheral!( }; ); -#[cfg(all(not(adc_f1), not(adc_v1)))] -foreach_peripheral!( - (adccommon, $inst:ident) => { - impl sealed::Common for peripherals::$inst { - fn regs() -> &'static crate::pac::adccommon::AdcCommon { - &crate::pac::$inst - } - } - - impl crate::adc::Common for peripherals::$inst {} - }; -); - macro_rules! impl_adc_pin { ($inst:ident, $pin:ident, $ch:expr) => { impl crate::adc::AdcPin for crate::peripherals::$pin {} diff --git a/embassy-stm32/src/adc/resolution.rs b/embassy-stm32/src/adc/resolution.rs new file mode 100644 index 000000000..62b52a46c --- /dev/null +++ b/embassy-stm32/src/adc/resolution.rs @@ -0,0 +1,63 @@ +#[cfg(any(adc_v2, adc_v3, adc_g0))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Resolution { + TwelveBit, + TenBit, + EightBit, + SixBit, +} + +#[cfg(adc_v4)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Resolution { + SixteenBit, + FourteenBit, + TwelveBit, + TenBit, + EightBit, +} + +impl Default for Resolution { + fn default() -> Self { + #[cfg(any(adc_v2, adc_v3, adc_g0))] + { + Self::TwelveBit + } + #[cfg(adc_v4)] + { + Self::SixteenBit + } + } +} + +impl From for crate::pac::adc::vals::Res { + fn from(res: Resolution) -> crate::pac::adc::vals::Res { + match res { + #[cfg(adc_v4)] + Resolution::SixteenBit => crate::pac::adc::vals::Res::SIXTEENBIT, + #[cfg(adc_v4)] + Resolution::FourteenBit => crate::pac::adc::vals::Res::FOURTEENBITV, + Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, + Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, + Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, + #[cfg(any(adc_v2, adc_v3, adc_g0))] + Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, + } + } +} + +impl Resolution { + pub fn to_max_count(&self) -> u32 { + match self { + #[cfg(adc_v4)] + Resolution::SixteenBit => (1 << 16) - 1, + #[cfg(adc_v4)] + Resolution::FourteenBit => (1 << 14) - 1, + Resolution::TwelveBit => (1 << 12) - 1, + Resolution::TenBit => (1 << 10) - 1, + Resolution::EightBit => (1 << 8) - 1, + #[cfg(any(adc_v2, adc_v3, adc_g0))] + Resolution::SixBit => (1 << 6) - 1, + } + } +} diff --git a/embassy-stm32/src/adc/sample_time.rs b/embassy-stm32/src/adc/sample_time.rs new file mode 100644 index 000000000..60ba80048 --- /dev/null +++ b/embassy-stm32/src/adc/sample_time.rs @@ -0,0 +1,111 @@ +macro_rules! impl_sample_time { + ($default_doc:expr, $default:ident, $pac:ty, ($(($doc:expr, $variant:ident, $pac_variant:ident)),*)) => { + #[doc = concat!("ADC sample time\n\nThe default setting is ", $default_doc, " ADC clock cycles.")] + #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] + pub enum SampleTime { + $( + #[doc = concat!($doc, " ADC clock cycles.")] + $variant, + )* + } + + impl From for $pac { + fn from(sample_time: SampleTime) -> $pac { + match sample_time { + $(SampleTime::$variant => <$pac>::$pac_variant),* + } + } + } + + impl Default for SampleTime { + fn default() -> Self { + Self::$default + } + } + }; +} + +#[cfg(adc_f1)] +impl_sample_time!( + "1.5", + Cycles1_5, + crate::pac::adc::vals::SampleTime, + ( + ("1.5", Cycles1_5, CYCLES1_5), + ("7.5", Cycles7_5, CYCLES7_5), + ("13.5", Cycles13_5, CYCLES13_5), + ("28.5", Cycles28_5, CYCLES28_5), + ("41.5", Cycles41_5, CYCLES41_5), + ("55.5", Cycles55_5, CYCLES55_5), + ("71.5", Cycles71_5, CYCLES71_5), + ("239.5", Cycles239_5, CYCLES239_5) + ) +); + +#[cfg(adc_v2)] +impl_sample_time!( + "3", + Cycles3, + crate::pac::adc::vals::Smp, + ( + ("3", Cycles3, CYCLES3), + ("15", Cycles15, CYCLES15), + ("28", Cycles28, CYCLES28), + ("56", Cycles56, CYCLES56), + ("84", Cycles84, CYCLES84), + ("112", Cycles112, CYCLES112), + ("144", Cycles144, CYCLES144), + ("480", Cycles480, CYCLES480) + ) +); + +#[cfg(adc_v3)] +impl_sample_time!( + "2.5", + Cycles2_5, + crate::pac::adc::vals::SampleTime, + ( + ("2.5", Cycles2_5, CYCLES2_5), + ("6.5", Cycles6_5, CYCLES6_5), + ("12.5", Cycles12_5, CYCLES12_5), + ("24.5", Cycles24_5, CYCLES24_5), + ("47.5", Cycles47_5, CYCLES47_5), + ("92.5", Cycles92_5, CYCLES92_5), + ("247.5", Cycles247_5, CYCLES247_5), + ("640.5", Cycles640_5, CYCLES640_5) + ) +); + +#[cfg(adc_g0)] +impl_sample_time!( + "1.5", + Cycles1_5, + crate::pac::adc::vals::SampleTime, + ( + ("1.5", Cycles1_5, CYCLES1_5), + ("3.5", Cycles3_5, CYCLES3_5), + ("7.5", Cycles7_5, CYCLES7_5), + ("12.5", Cycles12_5, CYCLES12_5), + ("19.5", Cycles19_5, CYCLES19_5), + ("39.5", Cycles39_5, CYCLES39_5), + ("79.5", Cycles79_5, CYCLES79_5), + ("160.5", Cycles160_5, CYCLES160_5) + ) +); + +#[cfg(adc_v4)] +impl_sample_time!( + "1.5", + Cycles1_5, + crate::pac::adc::vals::Smp, + ( + ("1.5", Cycles1_5, CYCLES1_5), + ("2.5", Cycles2_5, CYCLES2_5), + ("8.5", Cycles8_5, CYCLES8_5), + ("16.5", Cycles16_5, CYCLES16_5), + ("32.5", Cycles32_5, CYCLES32_5), + ("64.5", Cycles64_5, CYCLES64_5), + ("387.5", Cycles387_5, CYCLES387_5), + ("810.5", Cycles810_5, CYCLES810_5) + ) +); diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index 4fe4ad1f0..11a51f993 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -1,10 +1,8 @@ -use core::marker::PhantomData; - use embassy_hal_common::into_ref; use embedded_hal_02::blocking::delay::DelayUs; use super::InternalChannel; -use crate::adc::{AdcPin, Instance}; +use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime}; use crate::peripherals::ADC1; use crate::time::Hertz; use crate::Peripheral; @@ -17,39 +15,6 @@ pub const VREF_CALIB_MV: u32 = 3300; /// ADC turn-on time pub const ADC_POWERUP_TIME_US: u32 = 3; -pub enum Resolution { - TwelveBit, - TenBit, - EightBit, - SixBit, -} - -impl Default for Resolution { - fn default() -> Self { - Self::TwelveBit - } -} - -impl Resolution { - fn res(&self) -> crate::pac::adc::vals::Res { - match self { - Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, - Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, - Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, - Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, - } - } - - pub fn to_max_count(&self) -> u32 { - match self { - Resolution::TwelveBit => (1 << 12) - 1, - Resolution::TenBit => (1 << 10) - 1, - Resolution::EightBit => (1 << 8) - 1, - Resolution::SixBit => (1 << 6) - 1, - } - } -} - pub struct VrefInt; impl InternalChannel for VrefInt {} impl super::sealed::InternalChannel for VrefInt { @@ -80,15 +45,6 @@ impl super::sealed::InternalChannel for Temperature { } impl Temperature { - /// Converts temperature sensor reading in millivolts to degrees celcius - pub fn to_celcius(sample_mv: u16) -> f32 { - // From 6.3.22 Temperature sensor characteristics - const V25: i32 = 760; // mV - const AVG_SLOPE: f32 = 2.5; // mV/C - - (sample_mv as i32 - V25) as f32 / AVG_SLOPE + 25.0 - } - /// Time needed for temperature sensor readings to stabilize pub fn start_time_us() -> u32 { 10 @@ -103,42 +59,6 @@ impl super::sealed::InternalChannel for Vbat { } } -/// ADC sample time -/// -/// The default setting is 3 ADC clock cycles. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum SampleTime { - Cycles3 = 0b000, - Cycles15 = 0b001, - Cycles28 = 0b010, - Cycles56 = 0b011, - Cycles85 = 0b100, - Cycles112 = 0b101, - Cycles144 = 0b110, - Cycles480 = 0b111, -} - -impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::Smp { - match self { - SampleTime::Cycles3 => crate::pac::adc::vals::Smp::CYCLES3, - SampleTime::Cycles15 => crate::pac::adc::vals::Smp::CYCLES15, - SampleTime::Cycles28 => crate::pac::adc::vals::Smp::CYCLES28, - SampleTime::Cycles56 => crate::pac::adc::vals::Smp::CYCLES56, - SampleTime::Cycles85 => crate::pac::adc::vals::Smp::CYCLES84, - SampleTime::Cycles112 => crate::pac::adc::vals::Smp::CYCLES112, - SampleTime::Cycles144 => crate::pac::adc::vals::Smp::CYCLES144, - SampleTime::Cycles480 => crate::pac::adc::vals::Smp::CYCLES480, - } - } -} - -impl Default for SampleTime { - fn default() -> Self { - Self::Cycles3 - } -} - enum Prescaler { Div2, Div4, @@ -170,19 +90,12 @@ impl Prescaler { } } -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - vref_mv: u32, - resolution: Resolution, - phantom: PhantomData<&'d mut T>, -} - impl<'d, T> Adc<'d, T> where T: Instance, { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - into_ref!(_peri); + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + into_ref!(adc); T::enable(); T::reset(); @@ -198,10 +111,8 @@ where delay.delay_us(ADC_POWERUP_TIME_US); Self { + adc, sample_time: Default::default(), - resolution: Resolution::default(), - vref_mv: VREF_DEFAULT_MV, - phantom: PhantomData, } } @@ -210,19 +121,9 @@ where } pub fn set_resolution(&mut self, resolution: Resolution) { - self.resolution = resolution; - } - - /// Set VREF value in millivolts. This value is used for [to_millivolts()] sample conversion. - /// - /// Use this if you have a known precise VREF (VDDA) pin reference voltage. - pub fn set_vref_mv(&mut self, vref_mv: u32) { - self.vref_mv = vref_mv; - } - - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16 + unsafe { + T::regs().cr1().modify(|reg| reg.set_res(resolution.into())); + } } /// Enables internal voltage reference and returns [VrefInt], which can be used in @@ -306,7 +207,6 @@ where unsafe fn read_channel(&mut self, channel: u8) -> u16 { // Configure ADC - T::regs().cr1().modify(|reg| reg.set_res(self.resolution.res())); // Select channel T::regs().sqr3().write(|reg| reg.set_sq(0, channel)); @@ -320,14 +220,11 @@ where } unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); if ch <= 9 { - T::regs() - .smpr2() - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time)); } else { - T::regs() - .smpr1() - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } } diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 0f1090888..90aa7d3b9 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,9 +1,7 @@ -use core::marker::PhantomData; - use embassy_hal_common::into_ref; use embedded_hal_02::blocking::delay::DelayUs; -use crate::adc::{AdcPin, Instance}; +use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime}; use crate::Peripheral; /// Default VREF voltage used for sample conversion to millivolts. @@ -24,39 +22,6 @@ fn enable() { }); } -pub enum Resolution { - TwelveBit, - TenBit, - EightBit, - SixBit, -} - -impl Default for Resolution { - fn default() -> Self { - Self::TwelveBit - } -} - -impl Resolution { - fn res(&self) -> crate::pac::adc::vals::Res { - match self { - Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, - Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, - Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, - Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, - } - } - - pub fn to_max_count(&self) -> u32 { - match self { - Resolution::TwelveBit => (1 << 12) - 1, - Resolution::TenBit => (1 << 10) - 1, - Resolution::EightBit => (1 << 8) - 1, - Resolution::SixBit => (1 << 6) - 1, - } - } -} - pub struct VrefInt; impl AdcPin for VrefInt {} impl super::sealed::AdcPin for VrefInt { @@ -93,126 +58,9 @@ impl super::sealed::AdcPin for Vbat { } } -#[cfg(not(adc_g0))] -mod sample_time { - /// ADC sample time - /// - /// The default setting is 2.5 ADC clock cycles. - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] - pub enum SampleTime { - /// 2.5 ADC clock cycles - Cycles2_5 = 0b000, - - /// 6.5 ADC clock cycles - Cycles6_5 = 0b001, - - /// 12.5 ADC clock cycles - Cycles12_5 = 0b010, - - /// 24.5 ADC clock cycles - Cycles24_5 = 0b011, - - /// 47.5 ADC clock cycles - Cycles47_5 = 0b100, - - /// 92.5 ADC clock cycles - Cycles92_5 = 0b101, - - /// 247.5 ADC clock cycles - Cycles247_5 = 0b110, - - /// 640.5 ADC clock cycles - Cycles640_5 = 0b111, - } - - impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::SampleTime { - match self { - SampleTime::Cycles2_5 => crate::pac::adc::vals::SampleTime::CYCLES2_5, - SampleTime::Cycles6_5 => crate::pac::adc::vals::SampleTime::CYCLES6_5, - SampleTime::Cycles12_5 => crate::pac::adc::vals::SampleTime::CYCLES12_5, - SampleTime::Cycles24_5 => crate::pac::adc::vals::SampleTime::CYCLES24_5, - SampleTime::Cycles47_5 => crate::pac::adc::vals::SampleTime::CYCLES47_5, - SampleTime::Cycles92_5 => crate::pac::adc::vals::SampleTime::CYCLES92_5, - SampleTime::Cycles247_5 => crate::pac::adc::vals::SampleTime::CYCLES247_5, - SampleTime::Cycles640_5 => crate::pac::adc::vals::SampleTime::CYCLES640_5, - } - } - } - - impl Default for SampleTime { - fn default() -> Self { - Self::Cycles2_5 - } - } -} - -#[cfg(adc_g0)] -mod sample_time { - /// ADC sample time - /// - /// The default setting is 1.5 ADC clock cycles. - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] - pub enum SampleTime { - /// 1.5 ADC clock cycles - Cycles1_5 = 0b000, - - /// 3.5 ADC clock cycles - Cycles3_5 = 0b001, - - /// 7.5 ADC clock cycles - Cycles7_5 = 0b010, - - /// 12.5 ADC clock cycles - Cycles12_5 = 0b011, - - /// 19.5 ADC clock cycles - Cycles19_5 = 0b100, - - /// 39.5 ADC clock cycles - Cycles39_5 = 0b101, - - /// 79.5 ADC clock cycles - Cycles79_5 = 0b110, - - /// 160.5 ADC clock cycles - Cycles160_5 = 0b111, - } - - impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::SampleTime { - match self { - SampleTime::Cycles1_5 => crate::pac::adc::vals::SampleTime::CYCLES1_5, - SampleTime::Cycles3_5 => crate::pac::adc::vals::SampleTime::CYCLES3_5, - SampleTime::Cycles7_5 => crate::pac::adc::vals::SampleTime::CYCLES7_5, - SampleTime::Cycles12_5 => crate::pac::adc::vals::SampleTime::CYCLES12_5, - SampleTime::Cycles19_5 => crate::pac::adc::vals::SampleTime::CYCLES19_5, - SampleTime::Cycles39_5 => crate::pac::adc::vals::SampleTime::CYCLES39_5, - SampleTime::Cycles79_5 => crate::pac::adc::vals::SampleTime::CYCLES79_5, - SampleTime::Cycles160_5 => crate::pac::adc::vals::SampleTime::CYCLES160_5, - } - } - } - - impl Default for SampleTime { - fn default() -> Self { - Self::Cycles1_5 - } - } -} - -pub use sample_time::SampleTime; - -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - vref_mv: u32, - resolution: Resolution, - phantom: PhantomData<&'d mut T>, -} - impl<'d, T: Instance> Adc<'d, T> { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - into_ref!(_peri); + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + into_ref!(adc); enable(); unsafe { T::regs().cr().modify(|reg| { @@ -223,7 +71,7 @@ impl<'d, T: Instance> Adc<'d, T> { #[cfg(adc_g0)] T::regs().cfgr1().modify(|reg| { - reg.set_chselrmod(true); + reg.set_chselrmod(false); }); } @@ -242,10 +90,8 @@ impl<'d, T: Instance> Adc<'d, T> { delay.delay_us(1); Self { + adc, sample_time: Default::default(), - resolution: Resolution::default(), - vref_mv: VREF_DEFAULT_MV, - phantom: PhantomData, } } @@ -285,49 +131,17 @@ impl<'d, T: Instance> Adc<'d, T> { Vbat {} } - /// Calculates the system VDDA by sampling the internal VREFINT channel and comparing - /// the result with the value stored at the factory. If the chip's VDDA is not stable, run - /// this before each ADC conversion. - #[cfg(not(stm32g0))] // TODO is this supposed to be public? - #[allow(unused)] // TODO is this supposed to be public? - fn calibrate(&mut self, vrefint: &mut VrefInt) { - #[cfg(stm32l5)] - let vrefint_cal: u32 = todo!(); - #[cfg(not(stm32l5))] - let vrefint_cal = unsafe { crate::pac::VREFINTCAL.data().read().value() }; - let old_sample_time = self.sample_time; - - // "Table 24. Embedded internal voltage reference" states that the sample time needs to be - // at a minimum 4 us. With 640.5 ADC cycles we have a minimum of 8 us at 80 MHz, leaving - // some headroom. - self.sample_time = SampleTime::Cycles640_5; - - // This can't actually fail, it's just in a result to satisfy hal trait - let vrefint_samp = self.read(vrefint); - - self.sample_time = old_sample_time; - - self.vref_mv = (VREF_CALIB_MV * u32::from(vrefint_cal)) / u32::from(vrefint_samp); - } - pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; } pub fn set_resolution(&mut self, resolution: Resolution) { - self.resolution = resolution; - } - - /// Set VREF value in millivolts. This value is used for [to_millivolts()] sample conversion. - /// - /// Use this if you have a known precise VREF (VDDA) pin reference voltage. - pub fn set_vref_mv(&mut self, vref_mv: u32) { - self.vref_mv = vref_mv; - } - - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16 + unsafe { + #[cfg(not(stm32g0))] + T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); + #[cfg(stm32g0)] + T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into())); + } } /* @@ -379,12 +193,6 @@ impl<'d, T: Instance> Adc<'d, T> { // spin } - // Configure ADC - #[cfg(not(stm32g0))] - T::regs().cfgr().modify(|reg| reg.set_res(self.resolution.res())); - #[cfg(stm32g0)] - T::regs().cfgr1().modify(|reg| reg.set_res(self.resolution.res())); - // Configure channel Self::set_channel_sample_time(pin.channel(), self.sample_time); @@ -392,7 +200,7 @@ impl<'d, T: Instance> Adc<'d, T> { #[cfg(not(stm32g0))] T::regs().sqr1().write(|reg| reg.set_sq(0, pin.channel())); #[cfg(stm32g0)] - T::regs().chselr().write(|reg| reg.set_chsel(pin.channel() as u32)); + T::regs().chselr().write(|reg| reg.set_chsel(1 << pin.channel())); // Some models are affected by an erratum: // If we perform conversions slower than 1 kHz, the first read ADC value can be @@ -413,19 +221,16 @@ impl<'d, T: Instance> Adc<'d, T> { #[cfg(stm32g0)] unsafe fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { - T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.sample_time())); + T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into())); } #[cfg(not(stm32g0))] unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); if ch <= 9 { - T::regs() - .smpr1() - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + T::regs().smpr1().modify(|reg| reg.set_smp(ch as _, sample_time)); } else { - T::regs() - .smpr2() - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + T::regs().smpr2().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } } diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index eda2b2a72..4707b7c95 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -1,11 +1,10 @@ -use core::marker::PhantomData; +use core::sync::atomic::{AtomicU8, Ordering}; -use atomic_polyfill::{AtomicU8, Ordering}; use embedded_hal_02::blocking::delay::DelayUs; use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel}; use pac::adccommon::vals::Presc; -use super::{AdcPin, Instance, InternalChannel}; +use super::{Adc, AdcPin, Instance, InternalChannel, Resolution, SampleTime}; use crate::time::Hertz; use crate::{pac, Peripheral}; @@ -14,42 +13,6 @@ pub const VREF_DEFAULT_MV: u32 = 3300; /// VREF voltage used for factory calibration of VREFINTCAL register. pub const VREF_CALIB_MV: u32 = 3300; -pub enum Resolution { - SixteenBit, - FourteenBit, - TwelveBit, - TenBit, - EightBit, -} - -impl Default for Resolution { - fn default() -> Self { - Self::SixteenBit - } -} - -impl Resolution { - fn res(&self) -> pac::adc::vals::Res { - match self { - Resolution::SixteenBit => pac::adc::vals::Res::SIXTEENBIT, - Resolution::FourteenBit => pac::adc::vals::Res::FOURTEENBITV, - Resolution::TwelveBit => pac::adc::vals::Res::TWELVEBITV, - Resolution::TenBit => pac::adc::vals::Res::TENBIT, - Resolution::EightBit => pac::adc::vals::Res::EIGHTBIT, - } - } - - pub fn to_max_count(&self) -> u32 { - match self { - Resolution::SixteenBit => (1 << 16) - 1, - Resolution::FourteenBit => (1 << 14) - 1, - Resolution::TwelveBit => (1 << 12) - 1, - Resolution::TenBit => (1 << 10) - 1, - Resolution::EightBit => (1 << 8) - 1, - } - } -} - // NOTE: Vrefint/Temperature/Vbat are only available on ADC3 on H7, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs pub struct VrefInt; impl InternalChannel for VrefInt {} @@ -193,57 +156,6 @@ foreach_peripheral!( }; ); -/// ADC sample time -/// -/// The default setting is 2.5 ADC clock cycles. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum SampleTime { - /// 1.5 ADC clock cycles - Cycles1_5, - - /// 2.5 ADC clock cycles - Cycles2_5, - - /// 8.5 ADC clock cycles - Cycles8_5, - - /// 16.5 ADC clock cycles - Cycles16_5, - - /// 32.5 ADC clock cycles - Cycles32_5, - - /// 64.5 ADC clock cycles - Cycles64_5, - - /// 387.5 ADC clock cycles - Cycles387_5, - - /// 810.5 ADC clock cycles - Cycles810_5, -} - -impl SampleTime { - pub(crate) fn sample_time(&self) -> pac::adc::vals::Smp { - match self { - SampleTime::Cycles1_5 => pac::adc::vals::Smp::CYCLES1_5, - SampleTime::Cycles2_5 => pac::adc::vals::Smp::CYCLES2_5, - SampleTime::Cycles8_5 => pac::adc::vals::Smp::CYCLES8_5, - SampleTime::Cycles16_5 => pac::adc::vals::Smp::CYCLES16_5, - SampleTime::Cycles32_5 => pac::adc::vals::Smp::CYCLES32_5, - SampleTime::Cycles64_5 => pac::adc::vals::Smp::CYCLES64_5, - SampleTime::Cycles387_5 => pac::adc::vals::Smp::CYCLES387_5, - SampleTime::Cycles810_5 => pac::adc::vals::Smp::CYCLES810_5, - } - } -} - -impl Default for SampleTime { - fn default() -> Self { - Self::Cycles1_5 - } -} - // NOTE (unused): The prescaler enum closely copies the hardware capabilities, // but high prescaling doesn't make a lot of sense in the current implementation and is ommited. #[allow(unused)] @@ -312,16 +224,9 @@ impl Prescaler { } } -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - vref_mv: u32, - resolution: Resolution, - phantom: PhantomData<&'d mut T>, -} - -impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - embassy_hal_common::into_ref!(_peri); +impl<'d, T: Instance> Adc<'d, T> { + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + embassy_hal_common::into_ref!(adc); T::enable(); T::reset(); @@ -351,10 +256,8 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { } let mut s = Self { + adc, sample_time: Default::default(), - vref_mv: VREF_DEFAULT_MV, - resolution: Resolution::default(), - phantom: PhantomData, }; s.power_up(delay); s.configure_differential_inputs(); @@ -456,19 +359,9 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { } pub fn set_resolution(&mut self, resolution: Resolution) { - self.resolution = resolution; - } - - /// Set VREF value in millivolts. This value is used for [to_millivolts()] sample conversion. - /// - /// Use this if you have a known precise VREF (VDDA) pin reference voltage. - pub fn set_vref_mv(&mut self, vref_mv: u32) { - self.vref_mv = vref_mv; - } - - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16 + unsafe { + T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); + } } /// Perform a single conversion. @@ -509,9 +402,6 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { } unsafe fn read_channel(&mut self, channel: u8) -> u16 { - // Configure ADC - T::regs().cfgr().modify(|reg| reg.set_res(self.resolution.res())); - // Configure channel Self::set_channel_sample_time(channel, self.sample_time); @@ -528,14 +418,11 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { } unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); if ch <= 9 { - T::regs() - .smpr(0) - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + T::regs().smpr(0).modify(|reg| reg.set_smp(ch as _, sample_time)); } else { - T::regs() - .smpr(1) - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + T::regs().smpr(1).modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } } diff --git a/embassy-stm32/src/dma/bdma.rs b/embassy-stm32/src/dma/bdma.rs index 674255ddc..6160b9f40 100644 --- a/embassy-stm32/src/dma/bdma.rs +++ b/embassy-stm32/src/dma/bdma.rs @@ -3,6 +3,7 @@ use core::sync::atomic::{fence, Ordering}; use core::task::Waker; +use embassy_cortex_m::interrupt::Priority; use embassy_sync::waitqueue::AtomicWaker; use super::{TransferOptions, Word, WordSize}; @@ -38,10 +39,12 @@ impl State { static STATE: State = State::new(); /// safety: must be called only once -pub(crate) unsafe fn init() { +pub(crate) unsafe fn init(irq_priority: Priority) { foreach_interrupt! { ($peri:ident, bdma, $block:ident, $signal_name:ident, $irq:ident) => { - crate::interrupt::$irq::steal().enable(); + let irq = crate::interrupt::$irq::steal(); + irq.set_priority(irq_priority); + irq.enable(); }; } crate::_generated::init_bdma(); @@ -75,8 +78,7 @@ foreach_dma_channel! { ); } - unsafe fn start_write_repeated(&mut self, _request: Request, repeated: W, count: usize, reg_addr: *mut W, options: TransferOptions) { - let buf = [repeated]; + unsafe fn start_write_repeated(&mut self, _request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) { low_level_api::start_transfer( pac::$dma_peri, $channel_num, @@ -84,7 +86,7 @@ foreach_dma_channel! { _request, vals::Dir::FROMMEMORY, reg_addr as *const u32, - buf.as_ptr() as *mut u32, + repeated as *mut u32, count, false, vals::Size::from(W::bits()), diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index a45b8780b..fec60f708 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -1,6 +1,7 @@ use core::sync::atomic::{fence, Ordering}; use core::task::Waker; +use embassy_cortex_m::interrupt::Priority; use embassy_sync::waitqueue::AtomicWaker; use super::{Burst, FlowControl, Request, TransferOptions, Word, WordSize}; @@ -67,10 +68,12 @@ impl State { static STATE: State = State::new(); /// safety: must be called only once -pub(crate) unsafe fn init() { +pub(crate) unsafe fn init(irq_priority: Priority) { foreach_interrupt! { ($peri:ident, dma, $block:ident, $signal_name:ident, $irq:ident) => { - interrupt::$irq::steal().enable(); + let irq = interrupt::$irq::steal(); + irq.set_priority(irq_priority); + irq.enable(); }; } crate::_generated::init_dma(); @@ -99,15 +102,14 @@ foreach_dma_channel! { ) } - unsafe fn start_write_repeated(&mut self, request: Request, repeated: W, count: usize, reg_addr: *mut W, options: TransferOptions) { - let buf = [repeated]; + unsafe fn start_write_repeated(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) { low_level_api::start_transfer( pac::$dma_peri, $channel_num, request, vals::Dir::MEMORYTOPERIPHERAL, reg_addr as *const u32, - buf.as_ptr() as *mut u32, + repeated as *mut u32, count, false, vals::Size::from(W::bits()), diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index bde8c3ef3..d252cef3e 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -75,15 +75,14 @@ foreach_dma_channel! { ) } - unsafe fn start_write_repeated(&mut self, request: Request, repeated: W, count: usize, reg_addr: *mut W, options: TransferOptions) { - let buf = [repeated]; + unsafe fn start_write_repeated(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) { low_level_api::start_transfer( pac::$dma_peri, $channel_num, request, low_level_api::Dir::MemoryToPeripheral, reg_addr as *const u32, - buf.as_ptr() as *mut u32, + repeated as *mut u32, count, false, W::bits(), diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index cc030a93e..b51e0d40b 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -12,6 +12,8 @@ use core::mem; use core::pin::Pin; use core::task::{Context, Poll, Waker}; +#[cfg(any(dma, bdma))] +use embassy_cortex_m::interrupt::Priority; use embassy_hal_common::{impl_peripheral, into_ref}; #[cfg(dmamux)] @@ -57,7 +59,7 @@ pub(crate) mod sealed { unsafe fn start_write_repeated( &mut self, request: Request, - repeated: W, + repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions, @@ -244,7 +246,7 @@ mod transfers { pub fn write_repeated<'a, W: Word>( channel: impl Peripheral

+ 'a, request: Request, - repeated: W, + repeated: *const W, count: usize, reg_addr: *mut W, ) -> impl Future + 'a { @@ -294,11 +296,11 @@ pub struct NoDma; impl_peripheral!(NoDma); // safety: must be called only once at startup -pub(crate) unsafe fn init() { +pub(crate) unsafe fn init(#[cfg(bdma)] bdma_priority: Priority, #[cfg(dma)] dma_priority: Priority) { #[cfg(bdma)] - bdma::init(); + bdma::init(bdma_priority); #[cfg(dma)] - dma::init(); + dma::init(dma_priority); #[cfg(dmamux)] dmamux::init(); #[cfg(gpdma)] diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index 76a3dfab4..9f62b61ee 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -1,13 +1,125 @@ #![macro_use] -#[cfg(feature = "net")] #[cfg_attr(any(eth_v1a, eth_v1b, eth_v1c), path = "v1/mod.rs")] #[cfg_attr(eth_v2, path = "v2/mod.rs")] mod _version; pub mod generic_smi; -#[cfg(feature = "net")] -pub use _version::*; +use core::task::Context; + +use embassy_net_driver::{Capabilities, LinkState}; +use embassy_sync::waitqueue::AtomicWaker; + +pub use self::_version::*; + +#[allow(unused)] +const MTU: usize = 1514; +const TX_BUFFER_SIZE: usize = 1514; +const RX_BUFFER_SIZE: usize = 1536; + +#[repr(C, align(8))] +#[derive(Copy, Clone)] +pub(crate) struct Packet([u8; N]); + +pub struct PacketQueue { + tx_desc: [TDes; TX], + rx_desc: [RDes; RX], + tx_buf: [Packet; TX], + rx_buf: [Packet; RX], +} + +impl PacketQueue { + pub const fn new() -> Self { + const NEW_TDES: TDes = TDes::new(); + const NEW_RDES: RDes = RDes::new(); + Self { + tx_desc: [NEW_TDES; TX], + rx_desc: [NEW_RDES; RX], + tx_buf: [Packet([0; TX_BUFFER_SIZE]); TX], + rx_buf: [Packet([0; RX_BUFFER_SIZE]); RX], + } + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P> { + type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; + type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + WAKER.register(cx.waker()); + if self.rx.available().is_some() && self.tx.available().is_some() { + Some((RxToken { rx: &mut self.rx }, TxToken { tx: &mut self.tx })) + } else { + None + } + } + + fn transmit(&mut self, cx: &mut Context) -> Option> { + WAKER.register(cx.waker()); + if self.tx.available().is_some() { + Some(TxToken { tx: &mut self.tx }) + } else { + None + } + } + + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps.max_burst_size = Some(self.tx.len()); + caps + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + // TODO: wake cx.waker on link state change + cx.waker().wake_by_ref(); + if P::poll_link(self) { + LinkState::Up + } else { + LinkState::Down + } + } + + fn ethernet_address(&self) -> [u8; 6] { + self.mac_addr + } +} + +pub struct RxToken<'a, 'd> { + rx: &'a mut RDesRing<'d>, +} + +impl<'a, 'd> embassy_net_driver::RxToken for RxToken<'a, 'd> { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.available()); + let r = f(pkt); + self.rx.pop_packet(); + r + } +} + +pub struct TxToken<'a, 'd> { + tx: &'a mut TDesRing<'d>, +} + +impl<'a, 'd> embassy_net_driver::TxToken for TxToken<'a, 'd> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.available()); + let r = f(&mut pkt[..len]); + self.tx.transmit(len); + r + } +} /// Station Management Interface (SMI) on an ethernet PHY /// diff --git a/embassy-stm32/src/eth/v1/descriptors.rs b/embassy-stm32/src/eth/v1/descriptors.rs deleted file mode 100644 index 25f21ce19..000000000 --- a/embassy-stm32/src/eth/v1/descriptors.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::eth::_version::rx_desc::RDesRing; -use crate::eth::_version::tx_desc::TDesRing; - -pub struct DescriptorRing { - pub(crate) tx: TDesRing, - pub(crate) rx: RDesRing, -} - -impl DescriptorRing { - pub const fn new() -> Self { - Self { - tx: TDesRing::new(), - rx: RDesRing::new(), - } - } - - pub fn init(&mut self) { - self.tx.init(); - self.rx.init(); - } -} diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs index 38629a932..9c0f4d66d 100644 --- a/embassy-stm32/src/eth/v1/mod.rs +++ b/embassy-stm32/src/eth/v1/mod.rs @@ -1,16 +1,19 @@ // The v1c ethernet driver was ported to embassy from the awesome stm32-eth project (https://github.com/stm32-rs/stm32-eth). -use core::marker::PhantomData; +mod rx_desc; +mod tx_desc; + use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; +use embassy_cortex_m::interrupt::InterruptExt; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_net::{Device, DeviceCapabilities, LinkState, PacketBuf, MTU}; -use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; +pub(crate) use self::rx_desc::{RDes, RDesRing}; +pub(crate) use self::tx_desc::{TDes, TDesRing}; +use super::*; use crate::gpio::sealed::{AFType, Pin as __GpioPin}; -use crate::gpio::{AnyPin, Speed}; +use crate::gpio::AnyPin; #[cfg(eth_v1a)] use crate::pac::AFIO; #[cfg(any(eth_v1b, eth_v1c))] @@ -18,29 +21,16 @@ use crate::pac::SYSCFG; use crate::pac::{ETH, RCC}; use crate::Peripheral; -mod descriptors; -mod rx_desc; -mod tx_desc; +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, -use descriptors::DescriptorRing; -use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; - -use super::*; - -pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage>); -impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> { - pub const fn new() -> Self { - Self(StateStorage::new()) - } -} - -pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> { - state: PeripheralMutex<'d, Inner<'d, T, TX, RX>>, pins: [PeripheralRef<'d, AnyPin>; 9], _phy: P, clock_range: Cr, phy_addr: u8, - mac_addr: [u8; 6], + pub(crate) mac_addr: [u8; 6], } #[cfg(eth_v1a)] @@ -76,16 +66,16 @@ macro_rules! config_pins { critical_section::with(|_| { $( $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); - $pin.set_speed(Speed::VeryHigh); + $pin.set_speed(crate::gpio::Speed::VeryHigh); )* }) }; } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { /// safety: the returned instance is not leak-safe - pub unsafe fn new( - state: &'d mut State<'d, T, TX, RX>, + pub fn new( + queue: &'d mut PacketQueue, peri: impl Peripheral

+ 'd, interrupt: impl Peripheral

+ 'd, ref_clk: impl Peripheral

> + 'd, @@ -101,134 +91,131 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, mac_addr: [u8; 6], phy_addr: u8, ) -> Self { - into_ref!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + into_ref!(peri, interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - // Enable the necessary Clocks - // NOTE(unsafe) We have exclusive access to the registers - #[cfg(eth_v1a)] - critical_section::with(|_| { - RCC.apb2enr().modify(|w| w.set_afioen(true)); + unsafe { + // Enable the necessary Clocks + // NOTE(unsafe) We have exclusive access to the registers + #[cfg(eth_v1a)] + critical_section::with(|_| { + RCC.apb2enr().modify(|w| w.set_afioen(true)); - // Select RMII (Reduced Media Independent Interface) - // Must be done prior to enabling peripheral clock - AFIO.mapr().modify(|w| w.set_mii_rmii_sel(true)); + // Select RMII (Reduced Media Independent Interface) + // Must be done prior to enabling peripheral clock + AFIO.mapr().modify(|w| w.set_mii_rmii_sel(true)); - RCC.ahbenr().modify(|w| { - w.set_ethen(true); - w.set_ethtxen(true); - w.set_ethrxen(true); - }); - }); - - #[cfg(any(eth_v1b, eth_v1c))] - critical_section::with(|_| { - RCC.apb2enr().modify(|w| w.set_syscfgen(true)); - RCC.ahb1enr().modify(|w| { - w.set_ethen(true); - w.set_ethtxen(true); - w.set_ethrxen(true); + RCC.ahbenr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); }); - // RMII (Reduced Media Independent Interface) - SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(true)); - }); + #[cfg(any(eth_v1b, eth_v1c))] + critical_section::with(|_| { + RCC.apb2enr().modify(|w| w.set_syscfgen(true)); + RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); - #[cfg(eth_v1a)] - { - config_in_pins!(ref_clk, rx_d0, rx_d1); - config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_en); - } + // RMII (Reduced Media Independent Interface) + SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(true)); + }); - #[cfg(any(eth_v1b, eth_v1c))] - config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - - // NOTE(unsafe) We are ourselves not leak-safe. - let state = PeripheralMutex::new(interrupt, &mut state.0, || Inner::new(peri)); - - // NOTE(unsafe) We have exclusive access to the registers - let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); - - // Reset and wait - dma.dmabmr().modify(|w| w.set_sr(true)); - while dma.dmabmr().read().sr() {} - - mac.maccr().modify(|w| { - w.set_ifg(Ifg::IFG96); // inter frame gap 96 bit times - w.set_apcs(Apcs::STRIP); // automatic padding and crc stripping - w.set_fes(Fes::FES100); // fast ethernet speed - w.set_dm(Dm::FULLDUPLEX); // full duplex - // TODO: Carrier sense ? ECRSFD - }); - - // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, - // so the LR write must happen after the HR write. - mac.maca0hr() - .modify(|w| w.set_maca0h(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); - mac.maca0lr().write(|w| { - w.set_maca0l( - u32::from(mac_addr[0]) - | (u32::from(mac_addr[1]) << 8) - | (u32::from(mac_addr[2]) << 16) - | (u32::from(mac_addr[3]) << 24), - ) - }); - - // pause time - mac.macfcr().modify(|w| w.set_pt(0x100)); - - // Transfer and Forward, Receive and Forward - dma.dmaomr().modify(|w| { - w.set_tsf(Tsf::STOREFORWARD); - w.set_rsf(Rsf::STOREFORWARD); - }); - - dma.dmabmr().modify(|w| { - w.set_pbl(Pbl::PBL32) // programmable burst length - 32 ? - }); - - // TODO MTU size setting not found for v1 ethernet, check if correct - - // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called - let hclk = crate::rcc::get_freqs().ahb1; - let hclk_mhz = hclk.0 / 1_000_000; - - // Set the MDC clock frequency in the range 1MHz - 2.5MHz - let clock_range = match hclk_mhz { - 0..=24 => panic!("Invalid HCLK frequency - should be at least 25 MHz."), - 25..=34 => Cr::CR_20_35, // Divide by 16 - 35..=59 => Cr::CR_35_60, // Divide by 26 - 60..=99 => Cr::CR_60_100, // Divide by 42 - 100..=149 => Cr::CR_100_150, // Divide by 62 - 150..=216 => Cr::CR_150_168, // Divide by 102 - _ => { - panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") + #[cfg(eth_v1a)] + { + config_in_pins!(ref_clk, rx_d0, rx_d1); + config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_en); } - }; - let pins = [ - ref_clk.map_into(), - mdio.map_into(), - mdc.map_into(), - crs.map_into(), - rx_d0.map_into(), - rx_d1.map_into(), - tx_d0.map_into(), - tx_d1.map_into(), - tx_en.map_into(), - ]; + #[cfg(any(eth_v1b, eth_v1c))] + config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - let mut this = Self { - state, - pins, - _phy: phy, - clock_range, - phy_addr, - mac_addr, - }; + // NOTE(unsafe) We have exclusive access to the registers + let dma = ETH.ethernet_dma(); + let mac = ETH.ethernet_mac(); - this.state.with(|s| { - s.desc_ring.init(); + // Reset and wait + dma.dmabmr().modify(|w| w.set_sr(true)); + while dma.dmabmr().read().sr() {} + + mac.maccr().modify(|w| { + w.set_ifg(Ifg::IFG96); // inter frame gap 96 bit times + w.set_apcs(Apcs::STRIP); // automatic padding and crc stripping + w.set_fes(Fes::FES100); // fast ethernet speed + w.set_dm(Dm::FULLDUPLEX); // full duplex + // TODO: Carrier sense ? ECRSFD + }); + + // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, + // so the LR write must happen after the HR write. + mac.maca0hr() + .modify(|w| w.set_maca0h(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); + mac.maca0lr().write(|w| { + w.set_maca0l( + u32::from(mac_addr[0]) + | (u32::from(mac_addr[1]) << 8) + | (u32::from(mac_addr[2]) << 16) + | (u32::from(mac_addr[3]) << 24), + ) + }); + + // pause time + mac.macfcr().modify(|w| w.set_pt(0x100)); + + // Transfer and Forward, Receive and Forward + dma.dmaomr().modify(|w| { + w.set_tsf(Tsf::STOREFORWARD); + w.set_rsf(Rsf::STOREFORWARD); + }); + + dma.dmabmr().modify(|w| { + w.set_pbl(Pbl::PBL32) // programmable burst length - 32 ? + }); + + // TODO MTU size setting not found for v1 ethernet, check if correct + + // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called + let hclk = crate::rcc::get_freqs().ahb1; + let hclk_mhz = hclk.0 / 1_000_000; + + // Set the MDC clock frequency in the range 1MHz - 2.5MHz + let clock_range = match hclk_mhz { + 0..=24 => panic!("Invalid HCLK frequency - should be at least 25 MHz."), + 25..=34 => Cr::CR_20_35, // Divide by 16 + 35..=59 => Cr::CR_35_60, // Divide by 26 + 60..=99 => Cr::CR_60_100, // Divide by 42 + 100..=149 => Cr::CR_100_150, // Divide by 62 + 150..=216 => Cr::CR_150_168, // Divide by 102 + _ => { + panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") + } + }; + + let pins = [ + ref_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + crs.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_en.map_into(), + ]; + + let mut this = Self { + _peri: peri, + pins, + _phy: phy, + clock_range, + phy_addr, + mac_addr, + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), + }; fence(Ordering::SeqCst); @@ -245,23 +232,45 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, w.set_sr(DmaomrSr::STARTED); // start receiving channel }); + this.rx.demand_poll(); + // Enable interrupts dma.dmaier().modify(|w| { w.set_nise(true); w.set_rie(true); w.set_tie(true); }); - }); - P::phy_reset(&mut this); - P::phy_init(&mut this); - this + P::phy_reset(&mut this); + P::phy_init(&mut this); + + interrupt.set_handler(Self::on_interrupt); + interrupt.enable(); + + this + } + } + + fn on_interrupt(_cx: *mut ()) { + WAKER.wake(); + + // TODO: Check and clear more flags + unsafe { + let dma = ETH.ethernet_dma(); + + dma.dmasr().modify(|w| { + w.set_ts(true); + w.set_rs(true); + w.set_nis(true); + }); + // Delay two peripheral's clock + dma.dmasr().read(); + dma.dmasr().read(); + } } } -unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationManagement - for Ethernet<'d, T, P, TX, RX> -{ +unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> { fn smi_read(&mut self, reg: u8) -> u16 { // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` unsafe { @@ -297,44 +306,7 @@ unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationMa } } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Device for Ethernet<'d, T, P, TX, RX> { - fn is_transmit_ready(&mut self) -> bool { - self.state.with(|s| s.desc_ring.tx.available()) - } - - fn transmit(&mut self, pkt: PacketBuf) { - self.state.with(|s| unwrap!(s.desc_ring.tx.transmit(pkt))); - } - - fn receive(&mut self) -> Option { - self.state.with(|s| s.desc_ring.rx.pop_packet()) - } - - fn register_waker(&mut self, waker: &Waker) { - WAKER.register(waker); - } - - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); - caps.max_transmission_unit = MTU; - caps.max_burst_size = Some(TX.min(RX)); - caps - } - - fn link_state(&mut self) -> LinkState { - if P::poll_link(self) { - LinkState::Up - } else { - LinkState::Down - } - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} - -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { // NOTE(unsafe) We have `&mut self` and the interrupt doesn't use this registers unsafe { @@ -361,46 +333,3 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Etherne }) } } - -//---------------------------------------------------------------------- - -struct Inner<'d, T: Instance, const TX: usize, const RX: usize> { - _peri: PhantomData<&'d mut T>, - desc_ring: DescriptorRing, -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> { - pub fn new(_peri: impl Peripheral

+ 'd) -> Self { - Self { - _peri: PhantomData, - desc_ring: DescriptorRing::new(), - } - } -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> PeripheralState for Inner<'d, T, TX, RX> { - type Interrupt = crate::interrupt::ETH; - - fn on_interrupt(&mut self) { - unwrap!(self.desc_ring.tx.on_interrupt()); - self.desc_ring.rx.on_interrupt(); - - WAKER.wake(); - - // TODO: Check and clear more flags - unsafe { - let dma = ETH.ethernet_dma(); - - dma.dmasr().modify(|w| { - w.set_ts(true); - w.set_rs(true); - w.set_nis(true); - }); - // Delay two peripheral's clock - dma.dmasr().read(); - dma.dmasr().read(); - } - } -} - -static WAKER: AtomicWaker = AtomicWaker::new(); diff --git a/embassy-stm32/src/eth/v1/rx_desc.rs b/embassy-stm32/src/eth/v1/rx_desc.rs index d482590a1..8b8566d92 100644 --- a/embassy-stm32/src/eth/v1/rx_desc.rs +++ b/embassy-stm32/src/eth/v1/rx_desc.rs @@ -1,9 +1,9 @@ use core::sync::atomic::{compiler_fence, fence, Ordering}; -use embassy_net::{Packet, PacketBox, PacketBoxExt, PacketBuf}; -use stm32_metapac::eth::vals::{DmaomrSr, Rpd, Rps}; +use stm32_metapac::eth::vals::{Rpd, Rps}; use vcell::VolatileCell; +use crate::eth::RX_BUFFER_SIZE; use crate::pac::ETH; mod rx_consts { @@ -28,6 +28,8 @@ mod rx_consts { use rx_consts::*; +use super::Packet; + /// Receive Descriptor representation /// /// * rdes0: OWN and Status @@ -35,7 +37,7 @@ use rx_consts::*; /// * rdes2: data buffer address /// * rdes3: next descriptor address #[repr(C)] -struct RDes { +pub(crate) struct RDes { rdes0: VolatileCell, rdes1: VolatileCell, rdes2: VolatileCell, @@ -54,7 +56,7 @@ impl RDes { /// Return true if this RDes is acceptable to us #[inline(always)] - pub fn valid(&self) -> bool { + fn valid(&self) -> bool { // Write-back descriptor is valid if: // // Contains first buffer of packet AND contains last buf of @@ -64,15 +66,16 @@ impl RDes { /// Return true if this RDes is not currently owned by the DMA #[inline(always)] - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.rdes0.get() & RXDESC_0_OWN == 0 // Owned by us } /// Configures the reception buffer address and length and passed descriptor ownership to the DMA #[inline(always)] - pub fn set_ready(&mut self, buf_addr: u32, buf_len: usize) { - self.rdes1.set(self.rdes1.get() | (buf_len as u32) & RXDESC_1_RBS_MASK); - self.rdes2.set(buf_addr); + fn set_ready(&self, buf: *mut u8) { + self.rdes1 + .set(self.rdes1.get() | (RX_BUFFER_SIZE as u32) & RXDESC_1_RBS_MASK); + self.rdes2.set(buf as u32); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); @@ -88,12 +91,12 @@ impl RDes { // points to next descriptor (RCH) #[inline(always)] - fn set_buffer2(&mut self, buffer: *const u8) { + fn set_buffer2(&self, buffer: *const u8) { self.rdes3.set(buffer as u32); } #[inline(always)] - fn set_end_of_ring(&mut self) { + fn set_end_of_ring(&self) { self.rdes1.set(self.rdes1.get() | RXDESC_1_RER); } @@ -102,7 +105,7 @@ impl RDes { ((self.rdes0.get() >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize } - pub fn setup(&mut self, next: Option<&Self>) { + fn setup(&self, next: Option<&Self>, buf: *mut u8) { // Defer this initialization to this function, so we can have `RingEntry` on bss. self.rdes1.set(self.rdes1.get() | RXDESC_1_RCH); @@ -113,8 +116,11 @@ impl RDes { self.set_end_of_ring(); } } + + self.set_ready(buf); } } + /// Running state of the `RxRing` #[derive(PartialEq, Eq, Debug)] pub enum RunningState { @@ -123,116 +129,42 @@ pub enum RunningState { Running, } -impl RunningState { - /// whether self equals to `RunningState::Running` - pub fn is_running(&self) -> bool { - *self == RunningState::Running - } -} - /// Rx ring of descriptors and packets -/// -/// This ring has three major locations that work in lock-step. The DMA will never write to the tail -/// index, so the `read_index` must never pass the tail index. The `next_tail_index` is always 1 -/// slot ahead of the real tail index, and it must never pass the `read_index` or it could overwrite -/// a packet still to be passed to the application. -/// -/// nt can't pass r (no alloc) -/// +---+---+---+---+ Read ok +---+---+---+---+ No Read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation ok +---+---+---+---+ +---+---+---+---+ -/// ^ ^t ^t ^ ^t ^ -/// |r |r |r -/// |nt |nt |nt -/// -/// -/// +---+---+---+---+ Read ok +---+---+---+---+ Can't read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation fail +---+---+---+---+ Allocation ok +---+---+---+---+ -/// ^ ^t ^ ^t ^ ^ ^ ^t -/// |r | |r | | |r -/// |nt |nt |nt -/// -pub(crate) struct RDesRing { - descriptors: [RDes; N], - buffers: [Option; N], - read_index: usize, - next_tail_index: usize, +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, } -impl RDesRing { - pub const fn new() -> Self { - const RDES: RDes = RDes::new(); - const BUFFERS: Option = None; +impl<'a> RDesRing<'a> { + pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 1); + assert!(descriptors.len() == buffers.len()); - Self { - descriptors: [RDES; N], - buffers: [BUFFERS; N], - read_index: 0, - next_tail_index: 0, - } - } - - pub(crate) fn init(&mut self) { - assert!(N > 1); - let mut last_index = 0; - for (index, buf) in self.buffers.iter_mut().enumerate() { - let pkt = match PacketBox::new(Packet::new()) { - Some(p) => p, - None => { - if index == 0 { - panic!("Could not allocate at least one buffer for Ethernet receiving"); - } else { - break; - } - } - }; - self.descriptors[index].set_ready(pkt.as_ptr() as u32, pkt.len()); - *buf = Some(pkt); - last_index = index; - } - self.next_tail_index = (last_index + 1) % N; - - // not sure if this is supposed to span all of the descriptor or just those that contain buffers - { - let mut previous: Option<&mut RDes> = None; - for entry in self.descriptors.iter_mut() { - if let Some(prev) = &mut previous { - prev.setup(Some(entry)); - } - previous = Some(entry); - } - - if let Some(entry) = &mut previous { - entry.setup(None); - } + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1), buffers[i].0.as_mut_ptr()); } - // Register txdescriptor start + // Register rx descriptor start // NOTE (unsafe) Used for atomic writes unsafe { ETH.ethernet_dma() .dmardlar() - .write(|w| w.0 = &self.descriptors as *const _ as u32); + .write(|w| w.0 = descriptors.as_ptr() as u32); }; // We already have fences in `set_owned`, which is called in `setup` - // Start receive - unsafe { ETH.ethernet_dma().dmaomr().modify(|w| w.set_sr(DmaomrSr::STARTED)) }; - - self.demand_poll(); + Self { + descriptors, + buffers, + index: 0, + } } - fn demand_poll(&self) { + pub(crate) fn demand_poll(&self) { unsafe { ETH.ethernet_dma().dmarpdr().write(|w| w.set_rpd(Rpd::POLL)) }; } - pub(crate) fn on_interrupt(&mut self) { - // XXX: Do we need to do anything here ? Maybe we should try to advance the tail ptr, but it - // would soon hit the read ptr anyway, and we will wake smoltcp's stack on the interrupt - // which should try to pop a packet... - } - /// Get current `RunningState` fn running_state(&self) -> RunningState { match unsafe { ETH.ethernet_dma().dmasr().read().rps() } { @@ -252,52 +184,52 @@ impl RDesRing { } } - pub(crate) fn pop_packet(&mut self) -> Option { - if !self.running_state().is_running() { + /// Get a received packet if any, or None. + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + if self.running_state() != RunningState::Running { self.demand_poll(); } + // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the // buffer (I think .-.) fence(Ordering::SeqCst); - let read_available = self.descriptors[self.read_index].available(); - let tail_index = (self.next_tail_index + N - 1) % N; - - let pkt = if read_available && self.read_index != tail_index { - let pkt = self.buffers[self.read_index].take(); - let len = self.descriptors[self.read_index].packet_len(); - - assert!(pkt.is_some()); - let valid = self.descriptors[self.read_index].valid(); - - self.read_index = (self.read_index + 1) % N; - if valid { - pkt.map(|p| p.slice(0..len)) - } else { - None + // We might have to process many packets, in case some have been rx'd but are invalid. + loop { + let descriptor = &mut self.descriptors[self.index]; + if !descriptor.available() { + return None; } - } else { - None - }; - // Try to advance the tail_index - if self.next_tail_index != self.read_index { - match PacketBox::new(Packet::new()) { - Some(b) => { - let addr = b.as_ptr() as u32; - let buffer_len = b.len(); - self.buffers[self.next_tail_index].replace(b); - self.descriptors[self.next_tail_index].set_ready(addr, buffer_len); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - self.next_tail_index = (self.next_tail_index + 1) % N; - } - None => {} + // If packet is invalid, pop it and try again. + if !descriptor.valid() { + warn!("invalid packet: {:08x}", descriptor.rdes0.get()); + self.pop_packet(); + continue; } + + break; + } + + let descriptor = &mut self.descriptors[self.index]; + let len = descriptor.packet_len(); + return Some(&mut self.buffers[self.index].0[..len]); + } + + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); + + self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); + + self.demand_poll(); + + // Increment index. + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 } - pkt } } diff --git a/embassy-stm32/src/eth/v1/tx_desc.rs b/embassy-stm32/src/eth/v1/tx_desc.rs index f2889b550..0e63c5443 100644 --- a/embassy-stm32/src/eth/v1/tx_desc.rs +++ b/embassy-stm32/src/eth/v1/tx_desc.rs @@ -1,20 +1,10 @@ use core::sync::atomic::{compiler_fence, fence, Ordering}; -use embassy_net::PacketBuf; -use stm32_metapac::eth::vals::St; use vcell::VolatileCell; +use crate::eth::TX_BUFFER_SIZE; use crate::pac::ETH; -#[non_exhaustive] -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - NoBufferAvailable, - // TODO: Break down this error into several others - TransmissionError, -} - /// Transmit and Receive Descriptor fields #[allow(dead_code)] mod tx_consts { @@ -37,6 +27,8 @@ mod tx_consts { } use tx_consts::*; +use super::Packet; + /// Transmit Descriptor representation /// /// * tdes0: control @@ -44,7 +36,7 @@ use tx_consts::*; /// * tdes2: data buffer address /// * tdes3: next descriptor address #[repr(C)] -struct TDes { +pub(crate) struct TDes { tdes0: VolatileCell, tdes1: VolatileCell, tdes2: VolatileCell, @@ -62,7 +54,7 @@ impl TDes { } /// Return true if this TDes is not currently owned by the DMA - pub fn available(&self) -> bool { + fn available(&self) -> bool { (self.tdes0.get() & TXDESC_0_OWN) == 0 } @@ -79,26 +71,26 @@ impl TDes { fence(Ordering::SeqCst); } - fn set_buffer1(&mut self, buffer: *const u8) { + fn set_buffer1(&self, buffer: *const u8) { self.tdes2.set(buffer as u32); } - fn set_buffer1_len(&mut self, len: usize) { + fn set_buffer1_len(&self, len: usize) { self.tdes1 .set((self.tdes1.get() & !TXDESC_1_TBS_MASK) | ((len as u32) << TXDESC_1_TBS_SHIFT)); } // points to next descriptor (RCH) - fn set_buffer2(&mut self, buffer: *const u8) { + fn set_buffer2(&self, buffer: *const u8) { self.tdes3.set(buffer as u32); } - fn set_end_of_ring(&mut self) { + fn set_end_of_ring(&self) { self.tdes0.set(self.tdes0.get() | TXDESC_0_TER); } // set up as a part fo the ring buffer - configures the tdes - pub fn setup(&mut self, next: Option<&Self>) { + fn setup(&self, next: Option<&Self>) { // Defer this initialization to this function, so we can have `RingEntry` on bss. self.tdes0.set(TXDESC_0_TCH | TXDESC_0_IOC | TXDESC_0_FS | TXDESC_0_LS); match next { @@ -111,85 +103,58 @@ impl TDes { } } -pub(crate) struct TDesRing { - descriptors: [TDes; N], - buffers: [Option; N], - next_entry: usize, +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, } -impl TDesRing { - pub const fn new() -> Self { - const TDES: TDes = TDes::new(); - const BUFFERS: Option = None; - - Self { - descriptors: [TDES; N], - buffers: [BUFFERS; N], - next_entry: 0, - } - } - +impl<'a> TDesRing<'a> { /// Initialise this TDesRing. Assume TDesRing is corrupt - /// - /// The current memory address of the buffers inside this TDesRing - /// will be stored in the descriptors, so ensure the TDesRing is - /// not moved after initialisation. - pub(crate) fn init(&mut self) { - assert!(N > 0); + pub(crate) fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); - { - let mut previous: Option<&mut TDes> = None; - for entry in self.descriptors.iter_mut() { - if let Some(prev) = &mut previous { - prev.setup(Some(entry)); - } - previous = Some(entry); - } - - if let Some(entry) = &mut previous { - entry.setup(None); - } + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1)); } - self.next_entry = 0; // Register txdescriptor start // NOTE (unsafe) Used for atomic writes unsafe { ETH.ethernet_dma() .dmatdlar() - .write(|w| w.0 = &self.descriptors as *const _ as u32); + .write(|w| w.0 = descriptors.as_ptr() as u32); } - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - fence(Ordering::Release); - - // We don't need a compiler fence here because all interactions with `Descriptor` are - // volatiles - - // Start transmission - unsafe { ETH.ethernet_dma().dmaomr().modify(|w| w.set_st(St::STARTED)) }; - } - - /// Return true if a TDes is available for use - pub(crate) fn available(&self) -> bool { - self.descriptors[self.next_entry].available() - } - - pub(crate) fn transmit(&mut self, pkt: PacketBuf) -> Result<(), Error> { - if !self.available() { - return Err(Error::NoBufferAvailable); + Self { + descriptors, + buffers, + index: 0, } + } - let descriptor = &mut self.descriptors[self.next_entry]; + pub(crate) fn len(&self) -> usize { + self.descriptors.len() + } - let pkt_len = pkt.len(); - let address = pkt.as_ptr() as *const u8; + /// Return the next available packet buffer for transmitting, or None + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + let descriptor = &mut self.descriptors[self.index]; + if descriptor.available() { + Some(&mut self.buffers[self.index].0) + } else { + None + } + } - descriptor.set_buffer1(address); - descriptor.set_buffer1_len(pkt_len); + /// Transmit the packet written in a buffer returned by `available`. + pub(crate) fn transmit(&mut self, len: usize) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); - self.buffers[self.next_entry].replace(pkt); + descriptor.set_buffer1(self.buffers[self.index].0.as_ptr()); + descriptor.set_buffer1_len(len); descriptor.set_owned(); @@ -198,36 +163,12 @@ impl TDesRing { // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); - // Move the tail pointer (TPR) to the next descriptor - self.next_entry = (self.next_entry + 1) % N; - + // Move the index to the next descriptor + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 + } // Request the DMA engine to poll the latest tx descriptor unsafe { ETH.ethernet_dma().dmatpdr().modify(|w| w.0 = 1) } - Ok(()) - } - - pub(crate) fn on_interrupt(&mut self) -> Result<(), Error> { - let previous = (self.next_entry + N - 1) % N; - let td = &self.descriptors[previous]; - - // DMB to ensure that we are reading an updated value, probably not needed at the hardware - // level, but this is also a hint to the compiler that we're syncing on the buffer. - fence(Ordering::SeqCst); - - let tdes0 = td.tdes0.get(); - - if tdes0 & TXDESC_0_OWN != 0 { - // Transmission isn't done yet, probably a receive interrupt that fired this - return Ok(()); - } - - // Release the buffer - self.buffers[previous].take(); - - if tdes0 & TXDESC_0_ES != 0 { - Err(Error::TransmissionError) - } else { - Ok(()) - } } } diff --git a/embassy-stm32/src/eth/v2/descriptors.rs b/embassy-stm32/src/eth/v2/descriptors.rs index c6c06a9ce..2426596fb 100644 --- a/embassy-stm32/src/eth/v2/descriptors.rs +++ b/embassy-stm32/src/eth/v2/descriptors.rs @@ -1,19 +1,10 @@ use core::sync::atomic::{fence, Ordering}; -use embassy_net::{Packet, PacketBox, PacketBoxExt, PacketBuf}; use vcell::VolatileCell; +use crate::eth::{Packet, RX_BUFFER_SIZE, TX_BUFFER_SIZE}; use crate::pac::ETH; -#[non_exhaustive] -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - NoBufferAvailable, - // TODO: Break down this error into several others - TransmissionError, -} - /// Transmit and Receive Descriptor fields #[allow(dead_code)] mod emac_consts { @@ -41,7 +32,7 @@ use emac_consts::*; /// * tdes2: buffer lengths /// * tdes3: control and payload/frame length #[repr(C)] -struct TDes { +pub(crate) struct TDes { tdes0: VolatileCell, tdes1: VolatileCell, tdes2: VolatileCell, @@ -59,41 +50,26 @@ impl TDes { } /// Return true if this TDes is not currently owned by the DMA - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.tdes3.get() & EMAC_DES3_OWN == 0 } } -pub(crate) struct TDesRing { - td: [TDes; N], - buffers: [Option; N], - tdidx: usize, +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, } -impl TDesRing { - pub const fn new() -> Self { - const TDES: TDes = TDes::new(); - const BUFFERS: Option = None; +impl<'a> TDesRing<'a> { + /// Initialise this TDesRing. Assume TDesRing is corrupt. + pub fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); - Self { - td: [TDES; N], - buffers: [BUFFERS; N], - tdidx: 0, - } - } - - /// Initialise this TDesRing. Assume TDesRing is corrupt - /// - /// The current memory address of the buffers inside this TDesRing - /// will be stored in the descriptors, so ensure the TDesRing is - /// not moved after initialisation. - pub(crate) fn init(&mut self) { - assert!(N > 0); - - for td in self.td.iter_mut() { + for td in descriptors.iter_mut() { *td = TDes::new(); } - self.tdidx = 0; // Initialize the pointers in the DMA engine. (There will be a memory barrier later // before the DMA engine is enabled.) @@ -101,80 +77,60 @@ impl TDesRing { unsafe { let dma = ETH.ethernet_dma(); - dma.dmactx_dlar().write(|w| w.0 = &self.td as *const _ as u32); - dma.dmactx_rlr().write(|w| w.set_tdrl((N as u16) - 1)); - dma.dmactx_dtpr().write(|w| w.0 = &self.td[0] as *const _ as u32); + dma.dmactx_dlar().write(|w| w.0 = descriptors.as_mut_ptr() as u32); + dma.dmactx_rlr().write(|w| w.set_tdrl((descriptors.len() as u16) - 1)); + dma.dmactx_dtpr().write(|w| w.0 = 0); + } + + Self { + descriptors, + buffers, + index: 0, } } - /// Return true if a TDes is available for use - pub(crate) fn available(&self) -> bool { - self.td[self.tdidx].available() + pub(crate) fn len(&self) -> usize { + self.descriptors.len() } - pub(crate) fn transmit(&mut self, pkt: PacketBuf) -> Result<(), Error> { - if !self.available() { - return Err(Error::NoBufferAvailable); + /// Return the next available packet buffer for transmitting, or None + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + let d = &mut self.descriptors[self.index]; + if d.available() { + Some(&mut self.buffers[self.index].0) + } else { + None } - let x = self.tdidx; - let td = &mut self.td[x]; + } - let pkt_len = pkt.len(); - assert!(pkt_len as u32 <= EMAC_TDES2_B1L); - let address = pkt.as_ptr() as u32; + /// Transmit the packet written in a buffer returned by `available`. + pub(crate) fn transmit(&mut self, len: usize) { + let td = &mut self.descriptors[self.index]; + assert!(td.available()); + assert!(len as u32 <= EMAC_TDES2_B1L); // Read format - td.tdes0.set(address); - td.tdes2.set(pkt_len as u32 & EMAC_TDES2_B1L | EMAC_TDES2_IOC); + td.tdes0.set(self.buffers[self.index].0.as_ptr() as u32); + td.tdes2.set(len as u32 & EMAC_TDES2_B1L | EMAC_TDES2_IOC); // FD: Contains first buffer of packet // LD: Contains last buffer of packet // Give the DMA engine ownership td.tdes3.set(EMAC_DES3_FD | EMAC_DES3_LD | EMAC_DES3_OWN); - self.buffers[x].replace(pkt); - // Ensure changes to the descriptor are committed before DMA engine sees tail pointer store. // This will generate an DMB instruction. // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); - // Move the tail pointer (TPR) to the next descriptor - let x = (x + 1) % N; + self.index = self.index + 1; + if self.index == self.descriptors.len() { + self.index = 0; + } + + // signal DMA it can try again. // NOTE(unsafe) Atomic write - unsafe { - ETH.ethernet_dma() - .dmactx_dtpr() - .write(|w| w.0 = &self.td[x] as *const _ as u32); - } - self.tdidx = x; - Ok(()) - } - - pub(crate) fn on_interrupt(&mut self) -> Result<(), Error> { - let previous = (self.tdidx + N - 1) % N; - let td = &self.td[previous]; - - // DMB to ensure that we are reading an updated value, probably not needed at the hardware - // level, but this is also a hint to the compiler that we're syncing on the buffer. - fence(Ordering::SeqCst); - - let tdes3 = td.tdes3.get(); - - if tdes3 & EMAC_DES3_OWN != 0 { - // Transmission isn't done yet, probably a receive interrupt that fired this - return Ok(()); - } - assert!(tdes3 & EMAC_DES3_CTXT == 0); - - // Release the buffer - self.buffers[previous].take(); - - if tdes3 & EMAC_DES3_ES != 0 { - Err(Error::TransmissionError) - } else { - Ok(()) - } + unsafe { ETH.ethernet_dma().dmactx_dtpr().write(|w| w.0 = 0) } } } @@ -185,7 +141,7 @@ impl TDesRing { /// * rdes2: /// * rdes3: OWN and Status #[repr(C)] -struct RDes { +pub(crate) struct RDes { rdes0: VolatileCell, rdes1: VolatileCell, rdes2: VolatileCell, @@ -204,7 +160,7 @@ impl RDes { /// Return true if this RDes is acceptable to us #[inline(always)] - pub fn valid(&self) -> bool { + fn valid(&self) -> bool { // Write-back descriptor is valid if: // // Contains first buffer of packet AND contains last buf of @@ -215,177 +171,96 @@ impl RDes { /// Return true if this RDes is not currently owned by the DMA #[inline(always)] - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.rdes3.get() & EMAC_DES3_OWN == 0 // Owned by us } #[inline(always)] - pub fn set_ready(&mut self, buf_addr: u32) { - self.rdes0.set(buf_addr); + fn set_ready(&mut self, buf: *mut u8) { + self.rdes0.set(buf as u32); self.rdes3.set(EMAC_RDES3_BUF1V | EMAC_RDES3_IOC | EMAC_DES3_OWN); } } /// Rx ring of descriptors and packets -/// -/// This ring has three major locations that work in lock-step. The DMA will never write to the tail -/// index, so the `read_index` must never pass the tail index. The `next_tail_index` is always 1 -/// slot ahead of the real tail index, and it must never pass the `read_index` or it could overwrite -/// a packet still to be passed to the application. -/// -/// nt can't pass r (no alloc) -/// +---+---+---+---+ Read ok +---+---+---+---+ No Read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation ok +---+---+---+---+ +---+---+---+---+ -/// ^ ^t ^t ^ ^t ^ -/// |r |r |r -/// |nt |nt |nt -/// -/// -/// +---+---+---+---+ Read ok +---+---+---+---+ Can't read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation fail +---+---+---+---+ Allocation ok +---+---+---+---+ -/// ^ ^t ^ ^t ^ ^ ^ ^t -/// |r | |r | | |r -/// |nt |nt |nt -/// -pub(crate) struct RDesRing { - rd: [RDes; N], - buffers: [Option; N], - read_idx: usize, - next_tail_idx: usize, +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, } -impl RDesRing { - pub const fn new() -> Self { - const RDES: RDes = RDes::new(); - const BUFFERS: Option = None; +impl<'a> RDesRing<'a> { + pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 1); + assert!(descriptors.len() == buffers.len()); - Self { - rd: [RDES; N], - buffers: [BUFFERS; N], - read_idx: 0, - next_tail_idx: 0, - } - } - - pub(crate) fn init(&mut self) { - assert!(N > 1); - - for desc in self.rd.iter_mut() { + for (i, desc) in descriptors.iter_mut().enumerate() { *desc = RDes::new(); + desc.set_ready(buffers[i].0.as_mut_ptr()); } - let mut last_index = 0; - for (index, buf) in self.buffers.iter_mut().enumerate() { - let pkt = match PacketBox::new(Packet::new()) { - Some(p) => p, - None => { - if index == 0 { - panic!("Could not allocate at least one buffer for Ethernet receiving"); - } else { - break; - } - } - }; - let addr = pkt.as_ptr() as u32; - *buf = Some(pkt); - self.rd[index].set_ready(addr); - last_index = index; - } - self.next_tail_idx = (last_index + 1) % N; - unsafe { let dma = ETH.ethernet_dma(); - dma.dmacrx_dlar().write(|w| w.0 = self.rd.as_ptr() as u32); - dma.dmacrx_rlr().write(|w| w.set_rdrl((N as u16) - 1)); + dma.dmacrx_dlar().write(|w| w.0 = descriptors.as_mut_ptr() as u32); + dma.dmacrx_rlr().write(|w| w.set_rdrl((descriptors.len() as u16) - 1)); + dma.dmacrx_dtpr().write(|w| w.0 = 0); + } - // We manage to allocate all buffers, set the index to the last one, that means - // that the DMA won't consider the last one as ready, because it (unfortunately) - // stops at the tail ptr and wraps at the end of the ring, which means that we - // can't tell it to stop after the last buffer. - let tail_ptr = &self.rd[last_index] as *const _ as u32; - fence(Ordering::Release); - - dma.dmacrx_dtpr().write(|w| w.0 = tail_ptr); + Self { + descriptors, + buffers, + index: 0, } } - pub(crate) fn on_interrupt(&mut self) { - // XXX: Do we need to do anything here ? Maybe we should try to advance the tail ptr, but it - // would soon hit the read ptr anyway, and we will wake smoltcp's stack on the interrupt - // which should try to pop a packet... - } - - pub(crate) fn pop_packet(&mut self) -> Option { + /// Get a received packet if any, or None. + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the // buffer (I think .-.) fence(Ordering::SeqCst); - let read_available = self.rd[self.read_idx].available(); - let tail_index = (self.next_tail_idx + N - 1) % N; - - let pkt = if read_available && self.read_idx != tail_index { - let pkt = self.buffers[self.read_idx].take(); - let len = (self.rd[self.read_idx].rdes3.get() & EMAC_RDES3_PKTLEN) as usize; - - assert!(pkt.is_some()); - let valid = self.rd[self.read_idx].valid(); - - self.read_idx = (self.read_idx + 1) % N; - if valid { - pkt.map(|p| p.slice(0..len)) - } else { - None + // We might have to process many packets, in case some have been rx'd but are invalid. + loop { + let descriptor = &mut self.descriptors[self.index]; + if !descriptor.available() { + return None; } - } else { - None - }; - // Try to advance the tail_idx - if self.next_tail_idx != self.read_idx { - match PacketBox::new(Packet::new()) { - Some(b) => { - let addr = b.as_ptr() as u32; - self.buffers[self.next_tail_idx].replace(b); - self.rd[self.next_tail_idx].set_ready(addr); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - // NOTE(unsafe) atomic write - unsafe { - ETH.ethernet_dma() - .dmacrx_dtpr() - .write(|w| w.0 = &self.rd[self.next_tail_idx] as *const _ as u32); - } - - self.next_tail_idx = (self.next_tail_idx + 1) % N; - } - None => {} + // If packet is invalid, pop it and try again. + if !descriptor.valid() { + warn!("invalid packet: {:08x}", descriptor.rdes0.get()); + self.pop_packet(); + continue; } + + break; } - pkt + + let descriptor = &mut self.descriptors[self.index]; + let len = (descriptor.rdes3.get() & EMAC_RDES3_PKTLEN) as usize; + return Some(&mut self.buffers[self.index].0[..len]); } -} -pub struct DescriptorRing { - pub(crate) tx: TDesRing, - pub(crate) rx: RDesRing, -} + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); -impl DescriptorRing { - pub const fn new() -> Self { - Self { - tx: TDesRing::new(), - rx: RDesRing::new(), + self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::Release); + + // signal DMA it can try again. + // NOTE(unsafe) Atomic write + unsafe { ETH.ethernet_dma().dmacrx_dtpr().write(|w| w.0 = 0) } + + // Increment index. + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 } } - - pub fn init(&mut self) { - self.tx.init(); - self.rx.init(); - } } diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs index a81ee1183..fcb4a296c 100644 --- a/embassy-stm32/src/eth/v2/mod.rs +++ b/embassy-stm32/src/eth/v2/mod.rs @@ -1,35 +1,28 @@ -use core::marker::PhantomData; +mod descriptors; + use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; +use embassy_cortex_m::interrupt::InterruptExt; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_net::{Device, DeviceCapabilities, LinkState, PacketBuf, MTU}; -use embassy_sync::waitqueue::AtomicWaker; +pub(crate) use self::descriptors::{RDes, RDesRing, TDes, TDesRing}; +use super::*; use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::{AnyPin, Speed}; use crate::pac::{ETH, RCC, SYSCFG}; use crate::Peripheral; -mod descriptors; -use descriptors::DescriptorRing; +const MTU: usize = 1514; // 14 Ethernet header + 1500 IP packet -use super::*; - -pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage>); -impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> { - pub const fn new() -> Self { - Self(StateStorage::new()) - } -} -pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> { - state: PeripheralMutex<'d, Inner<'d, T, TX, RX>>, +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, pins: [PeripheralRef<'d, AnyPin>; 9], _phy: P, clock_range: u8, phy_addr: u8, - mac_addr: [u8; 6], + pub(crate) mac_addr: [u8; 6], } macro_rules! config_pins { @@ -44,10 +37,9 @@ macro_rules! config_pins { }; } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, P, TX, RX> { - /// safety: the returned instance is not leak-safe - pub unsafe fn new( - state: &'d mut State<'d, T, TX, RX>, +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + pub fn new( + queue: &'d mut PacketQueue, peri: impl Peripheral

+ 'd, interrupt: impl Peripheral

+ 'd, ref_clk: impl Peripheral

> + 'd, @@ -63,108 +55,123 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, mac_addr: [u8; 6], phy_addr: u8, ) -> Self { - into_ref!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + into_ref!(peri, interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - // Enable the necessary Clocks - // NOTE(unsafe) We have exclusive access to the registers - critical_section::with(|_| { - RCC.apb4enr().modify(|w| w.set_syscfgen(true)); - RCC.ahb1enr().modify(|w| { - w.set_eth1macen(true); - w.set_eth1txen(true); - w.set_eth1rxen(true); + unsafe { + // Enable the necessary Clocks + // NOTE(unsafe) We have exclusive access to the registers + critical_section::with(|_| { + RCC.apb4enr().modify(|w| w.set_syscfgen(true)); + RCC.ahb1enr().modify(|w| { + w.set_eth1macen(true); + w.set_eth1txen(true); + w.set_eth1rxen(true); + }); + + // RMII + SYSCFG.pmcr().modify(|w| w.set_epis(0b100)); }); - // RMII - SYSCFG.pmcr().modify(|w| w.set_epis(0b100)); - }); + config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + // NOTE(unsafe) We have exclusive access to the registers + let dma = ETH.ethernet_dma(); + let mac = ETH.ethernet_mac(); + let mtl = ETH.ethernet_mtl(); - // NOTE(unsafe) We are ourselves not leak-safe. - let state = PeripheralMutex::new(interrupt, &mut state.0, || Inner::new(peri)); + // Reset and wait + dma.dmamr().modify(|w| w.set_swr(true)); + while dma.dmamr().read().swr() {} - // NOTE(unsafe) We have exclusive access to the registers - let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); - let mtl = ETH.ethernet_mtl(); + mac.maccr().modify(|w| { + w.set_ipg(0b000); // 96 bit times + w.set_acs(true); + w.set_fes(true); + w.set_dm(true); + // TODO: Carrier sense ? ECRSFD + }); - // Reset and wait - dma.dmamr().modify(|w| w.set_swr(true)); - while dma.dmamr().read().swr() {} + // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, + // so the LR write must happen after the HR write. + mac.maca0hr() + .modify(|w| w.set_addrhi(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); + mac.maca0lr().write(|w| { + w.set_addrlo( + u32::from(mac_addr[0]) + | (u32::from(mac_addr[1]) << 8) + | (u32::from(mac_addr[2]) << 16) + | (u32::from(mac_addr[3]) << 24), + ) + }); - mac.maccr().modify(|w| { - w.set_ipg(0b000); // 96 bit times - w.set_acs(true); - w.set_fes(true); - w.set_dm(true); - // TODO: Carrier sense ? ECRSFD - }); + mac.macqtx_fcr().modify(|w| w.set_pt(0x100)); - // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, - // so the LR write must happen after the HR write. - mac.maca0hr() - .modify(|w| w.set_addrhi(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); - mac.maca0lr().write(|w| { - w.set_addrlo( - u32::from(mac_addr[0]) - | (u32::from(mac_addr[1]) << 8) - | (u32::from(mac_addr[2]) << 16) - | (u32::from(mac_addr[3]) << 24), - ) - }); + // disable all MMC RX interrupts + mac.mmc_rx_interrupt_mask().write(|w| { + w.set_rxcrcerpim(true); + w.set_rxalgnerpim(true); + w.set_rxucgpim(true); + w.set_rxlpiuscim(true); + w.set_rxlpitrcim(true) + }); - mac.macqtx_fcr().modify(|w| w.set_pt(0x100)); + // disable all MMC TX interrupts + mac.mmc_tx_interrupt_mask().write(|w| { + w.set_txscolgpim(true); + w.set_txmcolgpim(true); + w.set_txgpktim(true); + w.set_txlpiuscim(true); + w.set_txlpitrcim(true); + }); - mtl.mtlrx_qomr().modify(|w| w.set_rsf(true)); - mtl.mtltx_qomr().modify(|w| w.set_tsf(true)); + mtl.mtlrx_qomr().modify(|w| w.set_rsf(true)); + mtl.mtltx_qomr().modify(|w| w.set_tsf(true)); - dma.dmactx_cr().modify(|w| w.set_txpbl(1)); // 32 ? - dma.dmacrx_cr().modify(|w| { - w.set_rxpbl(1); // 32 ? - w.set_rbsz(MTU as u16); - }); + dma.dmactx_cr().modify(|w| w.set_txpbl(1)); // 32 ? + dma.dmacrx_cr().modify(|w| { + w.set_rxpbl(1); // 32 ? + w.set_rbsz(MTU as u16); + }); - // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called - let hclk = crate::rcc::get_freqs().ahb1; - let hclk_mhz = hclk.0 / 1_000_000; + // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called + let hclk = crate::rcc::get_freqs().ahb1; + let hclk_mhz = hclk.0 / 1_000_000; - // Set the MDC clock frequency in the range 1MHz - 2.5MHz - let clock_range = match hclk_mhz { - 0..=34 => 2, // Divide by 16 - 35..=59 => 3, // Divide by 26 - 60..=99 => 0, // Divide by 42 - 100..=149 => 1, // Divide by 62 - 150..=249 => 4, // Divide by 102 - 250..=310 => 5, // Divide by 124 - _ => { - panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") - } - }; + // Set the MDC clock frequency in the range 1MHz - 2.5MHz + let clock_range = match hclk_mhz { + 0..=34 => 2, // Divide by 16 + 35..=59 => 3, // Divide by 26 + 60..=99 => 0, // Divide by 42 + 100..=149 => 1, // Divide by 62 + 150..=249 => 4, // Divide by 102 + 250..=310 => 5, // Divide by 124 + _ => { + panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") + } + }; - let pins = [ - ref_clk.map_into(), - mdio.map_into(), - mdc.map_into(), - crs.map_into(), - rx_d0.map_into(), - rx_d1.map_into(), - tx_d0.map_into(), - tx_d1.map_into(), - tx_en.map_into(), - ]; + let pins = [ + ref_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + crs.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_en.map_into(), + ]; - let mut this = Self { - state, - pins, - _phy: phy, - clock_range, - phy_addr, - mac_addr, - }; - - this.state.with(|s| { - s.desc_ring.init(); + let mut this = Self { + _peri: peri, + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), + pins, + _phy: phy, + clock_range, + phy_addr, + mac_addr, + }; fence(Ordering::SeqCst); @@ -187,17 +194,37 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, w.set_rie(true); w.set_tie(true); }); - }); - P::phy_reset(&mut this); - P::phy_init(&mut this); - this + P::phy_reset(&mut this); + P::phy_init(&mut this); + + interrupt.set_handler(Self::on_interrupt); + interrupt.enable(); + + this + } + } + + fn on_interrupt(_cx: *mut ()) { + WAKER.wake(); + + // TODO: Check and clear more flags + unsafe { + let dma = ETH.ethernet_dma(); + + dma.dmacsr().modify(|w| { + w.set_ti(true); + w.set_ri(true); + w.set_nis(true); + }); + // Delay two peripheral's clock + dma.dmacsr().read(); + dma.dmacsr().read(); + } } } -unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationManagement - for Ethernet<'d, T, P, TX, RX> -{ +unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> { fn smi_read(&mut self, reg: u8) -> u16 { // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` unsafe { @@ -233,44 +260,7 @@ unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationMa } } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Device for Ethernet<'d, T, P, TX, RX> { - fn is_transmit_ready(&mut self) -> bool { - self.state.with(|s| s.desc_ring.tx.available()) - } - - fn transmit(&mut self, pkt: PacketBuf) { - self.state.with(|s| unwrap!(s.desc_ring.tx.transmit(pkt))); - } - - fn receive(&mut self) -> Option { - self.state.with(|s| s.desc_ring.rx.pop_packet()) - } - - fn register_waker(&mut self, waker: &Waker) { - WAKER.register(waker); - } - - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); - caps.max_transmission_unit = MTU; - caps.max_burst_size = Some(TX.min(RX)); - caps - } - - fn link_state(&mut self) -> LinkState { - if P::poll_link(self) { - LinkState::Up - } else { - LinkState::Down - } - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} - -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { // NOTE(unsafe) We have `&mut self` and the interrupt doesn't use this registers unsafe { @@ -307,46 +297,3 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Etherne }) } } - -//---------------------------------------------------------------------- - -struct Inner<'d, T: Instance, const TX: usize, const RX: usize> { - _peri: PhantomData<&'d mut T>, - desc_ring: DescriptorRing, -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> { - pub fn new(_peri: impl Peripheral

+ 'd) -> Self { - Self { - _peri: PhantomData, - desc_ring: DescriptorRing::new(), - } - } -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> PeripheralState for Inner<'d, T, TX, RX> { - type Interrupt = crate::interrupt::ETH; - - fn on_interrupt(&mut self) { - unwrap!(self.desc_ring.tx.on_interrupt()); - self.desc_ring.rx.on_interrupt(); - - WAKER.wake(); - - // TODO: Check and clear more flags - unsafe { - let dma = ETH.ethernet_dma(); - - dma.dmacsr().modify(|w| { - w.set_ti(true); - w.set_ri(true); - w.set_nis(true); - }); - // Delay two peripheral's clock - dma.dmacsr().read(); - dma.dmacsr().read(); - } - } -} - -static WAKER: AtomicWaker = AtomicWaker::new(); diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index dca991859..c9c3ef62a 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -25,11 +25,11 @@ fn cpu_regs() -> pac::exti::Exti { EXTI } -#[cfg(not(any(exti_g0, exti_l5, gpio_v1, exti_u5)))] +#[cfg(not(any(exti_c0, exti_g0, exti_l5, gpio_v1, exti_u5)))] fn exticr_regs() -> pac::syscfg::Syscfg { pac::SYSCFG } -#[cfg(any(exti_g0, exti_l5, exti_u5))] +#[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5))] fn exticr_regs() -> pac::exti::Exti { EXTI } @@ -39,9 +39,9 @@ fn exticr_regs() -> pac::afio::Afio { } pub unsafe fn on_irq() { - #[cfg(not(any(exti_g0, exti_l5, exti_u5)))] + #[cfg(not(any(exti_c0, exti_g0, exti_l5, exti_u5)))] let bits = EXTI.pr(0).read().0; - #[cfg(any(exti_g0, exti_l5, exti_u5))] + #[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5))] let bits = EXTI.rpr(0).read().0 | EXTI.fpr(0).read().0; // Mask all the channels that fired. @@ -53,9 +53,9 @@ pub unsafe fn on_irq() { } // Clear pending - #[cfg(not(any(exti_g0, exti_l5, exti_u5)))] + #[cfg(not(any(exti_c0, exti_g0, exti_l5, exti_u5)))] EXTI.pr(0).write_value(Lines(bits)); - #[cfg(any(exti_g0, exti_l5, exti_u5))] + #[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5))] { EXTI.rpr(0).write_value(Lines(bits)); EXTI.fpr(0).write_value(Lines(bits)); @@ -167,39 +167,33 @@ mod eh1 { } #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eha { - use futures::FutureExt; use super::*; impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for ExtiInput<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) } } } @@ -218,9 +212,9 @@ impl<'a> ExtiInputFuture<'a> { EXTI.ftsr(0).modify(|w| w.set_line(pin, falling)); // clear pending bit - #[cfg(not(any(exti_g0, exti_l5, exti_u5)))] + #[cfg(not(any(exti_c0, exti_g0, exti_l5, exti_u5)))] EXTI.pr(0).write(|w| w.set_line(pin, true)); - #[cfg(any(exti_g0, exti_l5, exti_u5))] + #[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5))] { EXTI.rpr(0).write(|w| w.set_line(pin, true)); EXTI.fpr(0).write(|w| w.set_line(pin, true)); diff --git a/embassy-stm32/src/flash/f4.rs b/embassy-stm32/src/flash/f4.rs index b8327ce4e..9e23a8adf 100644 --- a/embassy-stm32/src/flash/f4.rs +++ b/embassy-stm32/src/flash/f4.rs @@ -1,7 +1,6 @@ use core::convert::TryInto; use core::ptr::write_volatile; - -use atomic_polyfill::{fence, Ordering}; +use core::sync::atomic::{fence, Ordering}; use super::{ERASE_SIZE, FLASH_BASE, FLASH_SIZE}; use crate::flash::Error; diff --git a/embassy-stm32/src/flash/f7.rs b/embassy-stm32/src/flash/f7.rs index 6d47b78a0..dd0d8439d 100644 --- a/embassy-stm32/src/flash/f7.rs +++ b/embassy-stm32/src/flash/f7.rs @@ -1,7 +1,6 @@ use core::convert::TryInto; use core::ptr::write_volatile; - -use atomic_polyfill::{fence, Ordering}; +use core::sync::atomic::{fence, Ordering}; use crate::flash::Error; use crate::pac; diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs index 7ce0ac776..7de95ac11 100644 --- a/embassy-stm32/src/flash/h7.rs +++ b/embassy-stm32/src/flash/h7.rs @@ -39,6 +39,10 @@ pub(crate) unsafe fn blocking_write(offset: u32, buf: &[u8]) -> Result<(), Error w.set_psize(2); // 32 bits at once }); + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + let ret = { let mut ret: Result<(), Error> = Ok(()); let mut offset = offset; @@ -64,6 +68,10 @@ pub(crate) unsafe fn blocking_write(offset: u32, buf: &[u8]) -> Result<(), Error bank.cr().write(|w| w.set_pg(false)); + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + ret } @@ -71,24 +79,19 @@ pub(crate) unsafe fn blocking_erase(from: u32, to: u32) -> Result<(), Error> { let from = from - super::FLASH_BASE as u32; let to = to - super::FLASH_BASE as u32; - let bank_size = (super::FLASH_SIZE / 2) as u32; - - let (bank, start, end) = if to <= bank_size { + let (start, end) = if to <= super::FLASH_SIZE as u32 { let start_sector = from / super::ERASE_SIZE as u32; let end_sector = to / super::ERASE_SIZE as u32; - (0, start_sector, end_sector) - } else if from >= SECOND_BANK_OFFSET as u32 && to <= (SECOND_BANK_OFFSET as u32 + bank_size) { - let start_sector = (from - SECOND_BANK_OFFSET as u32) / super::ERASE_SIZE as u32; - let end_sector = (to - SECOND_BANK_OFFSET as u32) / super::ERASE_SIZE as u32; - (1, start_sector, end_sector) + (start_sector, end_sector) } else { - error!("Attempting to write outside of defined sectors"); + error!("Attempting to write outside of defined sectors {:x} {:x}", from, to); return Err(Error::Unaligned); }; - trace!("Erasing bank {}, sectors from {} to {}", bank, start, end); + trace!("Erasing sectors from {} to {}", start, end); for sector in start..end { - let ret = erase_sector(pac::FLASH.bank(bank), sector as u8); + let bank = if sector >= 8 { 1 } else { 0 }; + let ret = erase_sector(pac::FLASH.bank(bank), (sector % 8) as u8); if ret.is_err() { return ret; } diff --git a/embassy-stm32/src/fmc/mod.rs b/embassy-stm32/src/fmc.rs similarity index 52% rename from embassy-stm32/src/fmc/mod.rs rename to embassy-stm32/src/fmc.rs index 856a4adca..b9129cb51 100644 --- a/embassy-stm32/src/fmc/mod.rs +++ b/embassy-stm32/src/fmc.rs @@ -6,9 +6,6 @@ use crate::gpio::sealed::AFType; use crate::gpio::{Pull, Speed}; use crate::Peripheral; -mod pins; -pub use pins::*; - pub struct Fmc<'d, T: Instance> { peri: PhantomData<&'d mut T>, } @@ -19,7 +16,7 @@ unsafe impl<'d, T> stm32_fmc::FmcPeripheral for Fmc<'d, T> where T: Instance, { - const REGISTERS: *const () = crate::pac::FMC.0 as *const _; + const REGISTERS: *const () = T::REGS.0 as *const _; fn enable(&mut self) { ::enable(); @@ -27,9 +24,13 @@ where } fn memory_controller_enable(&mut self) { - // The FMCEN bit of the FMC_BCR2..4 registers is don’t - // care. It is only enabled through the FMC_BCR1 register. - unsafe { T::regs().bcr1().modify(|r| r.set_fmcen(true)) }; + // fmc v1 and v2 does not have the fmcen bit + // fsmc v1, v2 and v3 does not have the fmcen bit + // This is a "not" because it is expected that all future versions have this bit + #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1)))] + unsafe { + T::REGS.bcr1().modify(|r| r.set_fmcen(true)) + }; } fn source_clock_hz(&self) -> u32 { @@ -107,6 +108,24 @@ impl<'d, T: Instance> Fmc<'d, T> { ] )); + fmc_sdram_constructor!(sdram_a12bits_d16bits_4banks_bank2: ( + bank: stm32_fmc::SdramTargetBank::Bank2, + addr: [ + (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) + ], + ba: [(ba0: BA0Pin), (ba1: BA1Pin)], + d: [ + (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), + (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin) + ], + ctrl: [ + (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank2: ( bank: stm32_fmc::SdramTargetBank::Bank2, addr: [ @@ -128,13 +147,130 @@ impl<'d, T: Instance> Fmc<'d, T> { )); } +pub(crate) mod sealed { + pub trait Instance: crate::rcc::sealed::RccPeripheral { + const REGS: crate::pac::fmc::Fmc; + } +} + +pub trait Instance: sealed::Instance + 'static {} + foreach_peripheral!( (fmc, $inst:ident) => { impl crate::fmc::sealed::Instance for crate::peripherals::$inst { - fn regs() -> stm32_metapac::fmc::Fmc { - crate::pac::$inst - } + const REGS: crate::pac::fmc::Fmc = crate::pac::$inst; } impl crate::fmc::Instance for crate::peripherals::$inst {} }; ); + +pin_trait!(SDNWEPin, Instance); +pin_trait!(SDNCASPin, Instance); +pin_trait!(SDNRASPin, Instance); + +pin_trait!(SDNE0Pin, Instance); +pin_trait!(SDNE1Pin, Instance); + +pin_trait!(SDCKE0Pin, Instance); +pin_trait!(SDCKE1Pin, Instance); + +pin_trait!(SDCLKPin, Instance); + +pin_trait!(NBL0Pin, Instance); +pin_trait!(NBL1Pin, Instance); +pin_trait!(NBL2Pin, Instance); +pin_trait!(NBL3Pin, Instance); + +pin_trait!(INTPin, Instance); +pin_trait!(NLPin, Instance); +pin_trait!(NWaitPin, Instance); + +pin_trait!(NE1Pin, Instance); +pin_trait!(NE2Pin, Instance); +pin_trait!(NE3Pin, Instance); +pin_trait!(NE4Pin, Instance); + +pin_trait!(NCEPin, Instance); +pin_trait!(NOEPin, Instance); +pin_trait!(NWEPin, Instance); +pin_trait!(ClkPin, Instance); + +pin_trait!(BA0Pin, Instance); +pin_trait!(BA1Pin, Instance); + +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(D4Pin, Instance); +pin_trait!(D5Pin, Instance); +pin_trait!(D6Pin, Instance); +pin_trait!(D7Pin, Instance); +pin_trait!(D8Pin, Instance); +pin_trait!(D9Pin, Instance); +pin_trait!(D10Pin, Instance); +pin_trait!(D11Pin, Instance); +pin_trait!(D12Pin, Instance); +pin_trait!(D13Pin, Instance); +pin_trait!(D14Pin, Instance); +pin_trait!(D15Pin, Instance); +pin_trait!(D16Pin, Instance); +pin_trait!(D17Pin, Instance); +pin_trait!(D18Pin, Instance); +pin_trait!(D19Pin, Instance); +pin_trait!(D20Pin, Instance); +pin_trait!(D21Pin, Instance); +pin_trait!(D22Pin, Instance); +pin_trait!(D23Pin, Instance); +pin_trait!(D24Pin, Instance); +pin_trait!(D25Pin, Instance); +pin_trait!(D26Pin, Instance); +pin_trait!(D27Pin, Instance); +pin_trait!(D28Pin, Instance); +pin_trait!(D29Pin, Instance); +pin_trait!(D30Pin, Instance); +pin_trait!(D31Pin, Instance); + +pin_trait!(DA0Pin, Instance); +pin_trait!(DA1Pin, Instance); +pin_trait!(DA2Pin, Instance); +pin_trait!(DA3Pin, Instance); +pin_trait!(DA4Pin, Instance); +pin_trait!(DA5Pin, Instance); +pin_trait!(DA6Pin, Instance); +pin_trait!(DA7Pin, Instance); +pin_trait!(DA8Pin, Instance); +pin_trait!(DA9Pin, Instance); +pin_trait!(DA10Pin, Instance); +pin_trait!(DA11Pin, Instance); +pin_trait!(DA12Pin, Instance); +pin_trait!(DA13Pin, Instance); +pin_trait!(DA14Pin, Instance); +pin_trait!(DA15Pin, Instance); + +pin_trait!(A0Pin, Instance); +pin_trait!(A1Pin, Instance); +pin_trait!(A2Pin, Instance); +pin_trait!(A3Pin, Instance); +pin_trait!(A4Pin, Instance); +pin_trait!(A5Pin, Instance); +pin_trait!(A6Pin, Instance); +pin_trait!(A7Pin, Instance); +pin_trait!(A8Pin, Instance); +pin_trait!(A9Pin, Instance); +pin_trait!(A10Pin, Instance); +pin_trait!(A11Pin, Instance); +pin_trait!(A12Pin, Instance); +pin_trait!(A13Pin, Instance); +pin_trait!(A14Pin, Instance); +pin_trait!(A15Pin, Instance); +pin_trait!(A16Pin, Instance); +pin_trait!(A17Pin, Instance); +pin_trait!(A18Pin, Instance); +pin_trait!(A19Pin, Instance); +pin_trait!(A20Pin, Instance); +pin_trait!(A21Pin, Instance); +pin_trait!(A22Pin, Instance); +pin_trait!(A23Pin, Instance); +pin_trait!(A24Pin, Instance); +pin_trait!(A25Pin, Instance); diff --git a/embassy-stm32/src/fmc/pins.rs b/embassy-stm32/src/fmc/pins.rs deleted file mode 100644 index 5062e52ae..000000000 --- a/embassy-stm32/src/fmc/pins.rs +++ /dev/null @@ -1,118 +0,0 @@ -pub(crate) mod sealed { - pub trait Instance: crate::rcc::sealed::RccPeripheral { - fn regs() -> crate::pac::fmc::Fmc; - } -} - -pub trait Instance: sealed::Instance + 'static {} - -pin_trait!(SDNWEPin, Instance); -pin_trait!(SDNCASPin, Instance); -pin_trait!(SDNRASPin, Instance); - -pin_trait!(SDNE0Pin, Instance); -pin_trait!(SDNE1Pin, Instance); - -pin_trait!(SDCKE0Pin, Instance); -pin_trait!(SDCKE1Pin, Instance); - -pin_trait!(SDCLKPin, Instance); - -pin_trait!(NBL0Pin, Instance); -pin_trait!(NBL1Pin, Instance); -pin_trait!(NBL2Pin, Instance); -pin_trait!(NBL3Pin, Instance); - -pin_trait!(INTPin, Instance); -pin_trait!(NLPin, Instance); -pin_trait!(NWaitPin, Instance); - -pin_trait!(NE1Pin, Instance); -pin_trait!(NE2Pin, Instance); -pin_trait!(NE3Pin, Instance); -pin_trait!(NE4Pin, Instance); - -pin_trait!(NCEPin, Instance); -pin_trait!(NOEPin, Instance); -pin_trait!(NWEPin, Instance); -pin_trait!(ClkPin, Instance); - -pin_trait!(BA0Pin, Instance); -pin_trait!(BA1Pin, Instance); - -pin_trait!(D0Pin, Instance); -pin_trait!(D1Pin, Instance); -pin_trait!(D2Pin, Instance); -pin_trait!(D3Pin, Instance); -pin_trait!(D4Pin, Instance); -pin_trait!(D5Pin, Instance); -pin_trait!(D6Pin, Instance); -pin_trait!(D7Pin, Instance); -pin_trait!(D8Pin, Instance); -pin_trait!(D9Pin, Instance); -pin_trait!(D10Pin, Instance); -pin_trait!(D11Pin, Instance); -pin_trait!(D12Pin, Instance); -pin_trait!(D13Pin, Instance); -pin_trait!(D14Pin, Instance); -pin_trait!(D15Pin, Instance); -pin_trait!(D16Pin, Instance); -pin_trait!(D17Pin, Instance); -pin_trait!(D18Pin, Instance); -pin_trait!(D19Pin, Instance); -pin_trait!(D20Pin, Instance); -pin_trait!(D21Pin, Instance); -pin_trait!(D22Pin, Instance); -pin_trait!(D23Pin, Instance); -pin_trait!(D24Pin, Instance); -pin_trait!(D25Pin, Instance); -pin_trait!(D26Pin, Instance); -pin_trait!(D27Pin, Instance); -pin_trait!(D28Pin, Instance); -pin_trait!(D29Pin, Instance); -pin_trait!(D30Pin, Instance); -pin_trait!(D31Pin, Instance); - -pin_trait!(DA0Pin, Instance); -pin_trait!(DA1Pin, Instance); -pin_trait!(DA2Pin, Instance); -pin_trait!(DA3Pin, Instance); -pin_trait!(DA4Pin, Instance); -pin_trait!(DA5Pin, Instance); -pin_trait!(DA6Pin, Instance); -pin_trait!(DA7Pin, Instance); -pin_trait!(DA8Pin, Instance); -pin_trait!(DA9Pin, Instance); -pin_trait!(DA10Pin, Instance); -pin_trait!(DA11Pin, Instance); -pin_trait!(DA12Pin, Instance); -pin_trait!(DA13Pin, Instance); -pin_trait!(DA14Pin, Instance); -pin_trait!(DA15Pin, Instance); - -pin_trait!(A0Pin, Instance); -pin_trait!(A1Pin, Instance); -pin_trait!(A2Pin, Instance); -pin_trait!(A3Pin, Instance); -pin_trait!(A4Pin, Instance); -pin_trait!(A5Pin, Instance); -pin_trait!(A6Pin, Instance); -pin_trait!(A7Pin, Instance); -pin_trait!(A8Pin, Instance); -pin_trait!(A9Pin, Instance); -pin_trait!(A10Pin, Instance); -pin_trait!(A11Pin, Instance); -pin_trait!(A12Pin, Instance); -pin_trait!(A13Pin, Instance); -pin_trait!(A14Pin, Instance); -pin_trait!(A15Pin, Instance); -pin_trait!(A16Pin, Instance); -pin_trait!(A17Pin, Instance); -pin_trait!(A18Pin, Instance); -pin_trait!(A19Pin, Instance); -pin_trait!(A20Pin, Instance); -pin_trait!(A21Pin, Instance); -pin_trait!(A22Pin, Instance); -pin_trait!(A23Pin, Instance); -pin_trait!(A24Pin, Instance); -pin_trait!(A25Pin, Instance); diff --git a/embassy-stm32/src/gpio.rs b/embassy-stm32/src/gpio.rs index 5e3346754..3024f1ffa 100644 --- a/embassy-stm32/src/gpio.rs +++ b/embassy-stm32/src/gpio.rs @@ -28,6 +28,21 @@ impl<'d, T: Pin> Flex<'d, T> { Self { pin } } + #[inline] + pub fn degrade(mut self) -> Flex<'d, AnyPin> { + // Safety: We are about to drop the other copy of this pin, so + // this clone is safe. + let pin = unsafe { self.pin.clone_unchecked() }; + + // We don't want to run the destructor here, because that would + // deconfigure the pin. + core::mem::forget(self); + + Flex { + pin: pin.map_into::(), + } + } + /// Put the pin into input mode. #[inline] pub fn set_as_input(&mut self, pull: Pull) { @@ -286,6 +301,13 @@ impl<'d, T: Pin> Input<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Input<'d, AnyPin> { + Input { + pin: self.pin.degrade(), + } + } + #[inline] pub fn is_high(&self) -> bool { self.pin.is_high() @@ -345,6 +367,13 @@ impl<'d, T: Pin> Output<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Output<'d, AnyPin> { + Output { + pin: self.pin.degrade(), + } + } + /// Set the output as high. #[inline] pub fn set_high(&mut self) { @@ -407,6 +436,13 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Output<'d, AnyPin> { + Output { + pin: self.pin.degrade(), + } + } + #[inline] pub fn is_high(&self) -> bool { !self.pin.is_low() diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 9d314f411..f898fcc8b 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -7,6 +7,11 @@ use crate::interrupt::Interrupt; mod _version; pub use _version::*; +#[cfg(feature = "time")] +mod timeout; +#[cfg(feature = "time")] +pub use timeout::*; + use crate::peripherals; #[derive(Debug)] diff --git a/embassy-stm32/src/i2c/timeout.rs b/embassy-stm32/src/i2c/timeout.rs new file mode 100644 index 000000000..4fca1ca2b --- /dev/null +++ b/embassy-stm32/src/i2c/timeout.rs @@ -0,0 +1,142 @@ +use embassy_time::{Duration, Instant}; + +use super::{Error, I2c, Instance}; + +/// An I2C wrapper, which provides `embassy-time` based timeouts for all `embedded-hal` trait methods. +/// +/// This is useful for recovering from a shorted bus or a device stuck in a clock stretching state. +/// A regular [I2c] would freeze until condition is removed. +pub struct TimeoutI2c<'d, T: Instance, TXDMA, RXDMA> { + i2c: &'d mut I2c<'d, T, TXDMA, RXDMA>, + timeout: Duration, +} + +fn timeout_fn(timeout: Duration) -> impl Fn() -> Result<(), Error> { + let deadline = Instant::now() + timeout; + move || { + if Instant::now() > deadline { + Err(Error::Timeout) + } else { + Ok(()) + } + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> TimeoutI2c<'d, T, TXDMA, RXDMA> { + pub fn new(i2c: &'d mut I2c<'d, T, TXDMA, RXDMA>, timeout: Duration) -> Self { + Self { i2c, timeout } + } + + /// Blocking read with a custom timeout + pub fn blocking_read_timeout(&mut self, addr: u8, buffer: &mut [u8], timeout: Duration) -> Result<(), Error> { + self.i2c.blocking_read_timeout(addr, buffer, timeout_fn(timeout)) + } + + /// Blocking read with default timeout, provided in [`TimeoutI2c::new()`] + pub fn blocking_read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(addr, buffer, self.timeout) + } + + /// Blocking write with a custom timeout + pub fn blocking_write_timeout(&mut self, addr: u8, bytes: &[u8], timeout: Duration) -> Result<(), Error> { + self.i2c.blocking_write_timeout(addr, bytes, timeout_fn(timeout)) + } + + /// Blocking write with default timeout, provided in [`TimeoutI2c::new()`] + pub fn blocking_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + self.blocking_write_timeout(addr, bytes, self.timeout) + } + + /// Blocking write-read with a custom timeout + pub fn blocking_write_read_timeout( + &mut self, + addr: u8, + bytes: &[u8], + buffer: &mut [u8], + timeout: Duration, + ) -> Result<(), Error> { + self.i2c + .blocking_write_read_timeout(addr, bytes, buffer, timeout_fn(timeout)) + } + + /// Blocking write-read with default timeout, provided in [`TimeoutI2c::new()`] + pub fn blocking_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_write_read_timeout(addr, bytes, buffer, self.timeout) + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_02::blocking::i2c::Read for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + + fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(addr, buffer) + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_02::blocking::i2c::Write for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(addr, bytes) + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_02::blocking::i2c::WriteRead for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + + fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(addr, bytes, buffer) + } +} + +#[cfg(feature = "unstable-traits")] +mod eh1 { + use super::*; + + impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::ErrorType for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + } + + impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::I2c for TimeoutI2c<'d, T, TXDMA, RXDMA> { + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, buffer) + } + + fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, buffer) + } + + fn write_iter(&mut self, _address: u8, _bytes: B) -> Result<(), Self::Error> + where + B: IntoIterator, + { + todo!(); + } + + fn write_iter_read(&mut self, _address: u8, _bytes: B, _buffer: &mut [u8]) -> Result<(), Self::Error> + where + B: IntoIterator, + { + todo!(); + } + + fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, wr_buffer, rd_buffer) + } + + fn transaction<'a>( + &mut self, + _address: u8, + _operations: &mut [embedded_hal_1::i2c::Operation<'a>], + ) -> Result<(), Self::Error> { + todo!(); + } + + fn transaction_iter<'a, O>(&mut self, _address: u8, _operations: O) -> Result<(), Self::Error> + where + O: IntoIterator>, + { + todo!(); + } + } +} diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index f39a37df6..f140e2b0d 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -1,8 +1,9 @@ use core::marker::PhantomData; use embassy_embedded_hal::SetConfig; -use embassy_hal_common::into_ref; +use embassy_hal_common::{into_ref, PeripheralRef}; +use crate::dma::NoDma; use crate::gpio::sealed::AFType; use crate::gpio::Pull; use crate::i2c::{Error, Instance, SclPin, SdaPin}; @@ -34,19 +35,26 @@ impl State { } } -pub struct I2c<'d, T: Instance> { +pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> { phantom: PhantomData<&'d mut T>, + #[allow(dead_code)] + tx_dma: PeripheralRef<'d, TXDMA>, + #[allow(dead_code)] + rx_dma: PeripheralRef<'d, RXDMA>, } -impl<'d, T: Instance> I2c<'d, T> { +impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { pub fn new( _peri: impl Peripheral

+ 'd, scl: impl Peripheral

> + 'd, sda: impl Peripheral

> + 'd, + _irq: impl Peripheral

+ 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, freq: Hertz, config: Config, ) -> Self { - into_ref!(scl, sda); + into_ref!(scl, sda, tx_dma, rx_dma); T::enable(); T::reset(); @@ -99,7 +107,11 @@ impl<'d, T: Instance> I2c<'d, T> { }); } - Self { phantom: PhantomData } + Self { + phantom: PhantomData, + tx_dma, + rx_dma, + } } unsafe fn check_and_clear_error_flags(&self) -> Result { @@ -141,7 +153,12 @@ impl<'d, T: Instance> I2c<'d, T> { Ok(sr1) } - unsafe fn write_bytes(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + unsafe fn write_bytes( + &mut self, + addr: u8, + bytes: &[u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { // Send a START condition T::regs().cr1().modify(|reg| { @@ -149,7 +166,9 @@ impl<'d, T: Instance> I2c<'d, T> { }); // Wait until START condition was generated - while !self.check_and_clear_error_flags()?.start() {} + while !self.check_and_clear_error_flags()?.start() { + check_timeout()?; + } // Also wait until signalled we're master and everything is waiting for us while { @@ -157,7 +176,9 @@ impl<'d, T: Instance> I2c<'d, T> { let sr2 = T::regs().sr2().read(); !sr2.msl() && !sr2.busy() - } {} + } { + check_timeout()?; + } // Set up current address, we're trying to talk to T::regs().dr().write(|reg| reg.set_dr(addr << 1)); @@ -165,26 +186,30 @@ impl<'d, T: Instance> I2c<'d, T> { // Wait until address was sent // Wait for the address to be acknowledged // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set. - while !self.check_and_clear_error_flags()?.addr() {} + while !self.check_and_clear_error_flags()?.addr() { + check_timeout()?; + } // Clear condition by reading SR2 let _ = T::regs().sr2().read(); // Send bytes for c in bytes { - self.send_byte(*c)?; + self.send_byte(*c, &check_timeout)?; } // Fallthrough is success Ok(()) } - unsafe fn send_byte(&self, byte: u8) -> Result<(), Error> { + unsafe fn send_byte(&self, byte: u8, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { // Wait until we're ready for sending while { // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set. !self.check_and_clear_error_flags()?.txe() - } {} + } { + check_timeout()?; + } // Push out a byte of data T::regs().dr().write(|reg| reg.set_dr(byte)); @@ -193,24 +218,33 @@ impl<'d, T: Instance> I2c<'d, T> { while { // Check for any potential error conditions. !self.check_and_clear_error_flags()?.btf() - } {} + } { + check_timeout()?; + } Ok(()) } - unsafe fn recv_byte(&self) -> Result { + unsafe fn recv_byte(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result { while { // Check for any potential error conditions. self.check_and_clear_error_flags()?; !T::regs().sr1().read().rxne() - } {} + } { + check_timeout()?; + } let value = T::regs().dr().read().dr(); Ok(value) } - pub fn blocking_read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + pub fn blocking_read_timeout( + &mut self, + addr: u8, + buffer: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { if let Some((last, buffer)) = buffer.split_last_mut() { // Send a START condition and set ACK bit unsafe { @@ -221,27 +255,33 @@ impl<'d, T: Instance> I2c<'d, T> { } // Wait until START condition was generated - while unsafe { !T::regs().sr1().read().start() } {} + while unsafe { !self.check_and_clear_error_flags()?.start() } { + check_timeout()?; + } // Also wait until signalled we're master and everything is waiting for us while { let sr2 = unsafe { T::regs().sr2().read() }; !sr2.msl() && !sr2.busy() - } {} + } { + check_timeout()?; + } // Set up current address, we're trying to talk to unsafe { T::regs().dr().write(|reg| reg.set_dr((addr << 1) + 1)) } // Wait until address was sent // Wait for the address to be acknowledged - while unsafe { !self.check_and_clear_error_flags()?.addr() } {} + while unsafe { !self.check_and_clear_error_flags()?.addr() } { + check_timeout()?; + } // Clear condition by reading SR2 let _ = unsafe { T::regs().sr2().read() }; // Receive bytes into buffer for c in buffer { - *c = unsafe { self.recv_byte()? }; + *c = unsafe { self.recv_byte(&check_timeout)? }; } // Prepare to send NACK then STOP after next byte @@ -253,10 +293,12 @@ impl<'d, T: Instance> I2c<'d, T> { } // Receive last byte - *last = unsafe { self.recv_byte()? }; + *last = unsafe { self.recv_byte(&check_timeout)? }; // Wait for the STOP to be sent. - while unsafe { T::regs().cr1().read().stop() } {} + while unsafe { T::regs().cr1().read().stop() } { + check_timeout()?; + } // Fallthrough is success Ok(()) @@ -265,25 +307,50 @@ impl<'d, T: Instance> I2c<'d, T> { } } - pub fn blocking_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + pub fn blocking_read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(addr, buffer, || Ok(())) + } + + pub fn blocking_write_timeout( + &mut self, + addr: u8, + bytes: &[u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { unsafe { - self.write_bytes(addr, bytes)?; + self.write_bytes(addr, bytes, &check_timeout)?; // Send a STOP condition T::regs().cr1().modify(|reg| reg.set_stop(true)); // Wait for STOP condition to transmit. - while T::regs().cr1().read().stop() {} + while T::regs().cr1().read().stop() { + check_timeout()?; + } }; // Fallthrough is success Ok(()) } - pub fn blocking_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { - unsafe { self.write_bytes(addr, bytes)? }; - self.blocking_read(addr, buffer)?; + pub fn blocking_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + self.blocking_write_timeout(addr, bytes, || Ok(())) + } + + pub fn blocking_write_read_timeout( + &mut self, + addr: u8, + bytes: &[u8], + buffer: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + unsafe { self.write_bytes(addr, bytes, &check_timeout)? }; + self.blocking_read_timeout(addr, buffer, &check_timeout)?; Ok(()) } + + pub fn blocking_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_write_read_timeout(addr, bytes, buffer, || Ok(())) + } } impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> { diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 89b52da98..06ff07b21 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -1,8 +1,8 @@ use core::cmp; use core::future::poll_fn; +use core::sync::atomic::{AtomicUsize, Ordering}; use core::task::Poll; -use atomic_polyfill::{AtomicUsize, Ordering}; use embassy_embedded_hal::SetConfig; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; @@ -131,7 +131,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { if isr.tcr() || isr.tc() { let state = T::state(); - state.chunks_transferred.fetch_add(1, Ordering::Relaxed); + let transferred = state.chunks_transferred.load(Ordering::Relaxed); + state.chunks_transferred.store(transferred + 1, Ordering::Relaxed); state.waker.wake(); } // The flag can only be cleared by writting to nbytes, we won't do that here, so disable @@ -147,14 +148,23 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { } } - unsafe fn master_read(address: u8, length: usize, stop: Stop, reload: bool, restart: bool) { + unsafe fn master_read( + address: u8, + length: usize, + stop: Stop, + reload: bool, + restart: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { assert!(length < 256); if !restart { // Wait for any previous address sequence to end // automatically. This could be up to 50% of a bus // cycle (ie. up to 0.5/freq) - while T::regs().cr2().read().start() {} + while T::regs().cr2().read().start() { + check_timeout()?; + } } // Set START and prepare to receive bytes into @@ -176,15 +186,25 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_autoend(stop.autoend()); w.set_reload(reload); }); + + Ok(()) } - unsafe fn master_write(address: u8, length: usize, stop: Stop, reload: bool) { + unsafe fn master_write( + address: u8, + length: usize, + stop: Stop, + reload: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { assert!(length < 256); // Wait for any previous address sequence to end // automatically. This could be up to 50% of a bus // cycle (ie. up to 0.5/freq) - while T::regs().cr2().read().start() {} + while T::regs().cr2().read().start() { + check_timeout()?; + } let reload = if reload { i2c::vals::Reload::NOTCOMPLETED @@ -204,12 +224,20 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_autoend(stop.autoend()); w.set_reload(reload); }); + + Ok(()) } - unsafe fn master_continue(length: usize, reload: bool) { + unsafe fn master_continue( + length: usize, + reload: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { assert!(length < 256 && length > 0); - while !T::regs().isr().read().tcr() {} + while !T::regs().isr().read().tcr() { + check_timeout()?; + } let reload = if reload { i2c::vals::Reload::NOTCOMPLETED @@ -221,6 +249,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_nbytes(length as u8); w.set_reload(reload); }); + + Ok(()) } fn flush_txdr(&self) { @@ -243,7 +273,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { //} } - fn wait_txe(&self) -> Result<(), Error> { + fn wait_txe(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { loop { unsafe { let isr = T::regs().isr().read(); @@ -261,10 +291,12 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { return Err(Error::Nack); } } + + check_timeout()?; } } - fn wait_rxne(&self) -> Result<(), Error> { + fn wait_rxne(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { loop { unsafe { let isr = T::regs().isr().read(); @@ -282,10 +314,12 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { return Err(Error::Nack); } } + + check_timeout()?; } } - fn wait_tc(&self) -> Result<(), Error> { + fn wait_tc(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { loop { unsafe { let isr = T::regs().isr().read(); @@ -303,10 +337,18 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { return Err(Error::Nack); } } + + check_timeout()?; } } - fn read_internal(&mut self, address: u8, buffer: &mut [u8], restart: bool) -> Result<(), Error> { + fn read_internal( + &mut self, + address: u8, + buffer: &mut [u8], + restart: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { let completed_chunks = buffer.len() / 255; let total_chunks = if completed_chunks * 255 == buffer.len() { completed_chunks @@ -322,20 +364,21 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { Stop::Automatic, last_chunk_idx != 0, restart, - ); + &check_timeout, + )?; } for (number, chunk) in buffer.chunks_mut(255).enumerate() { if number != 0 { // NOTE(unsafe) We have &mut self unsafe { - Self::master_continue(chunk.len(), number != last_chunk_idx); + Self::master_continue(chunk.len(), number != last_chunk_idx, &check_timeout)?; } } for byte in chunk { // Wait until we have received something - self.wait_rxne()?; + self.wait_rxne(&check_timeout)?; unsafe { *byte = T::regs().rxdr().read().rxdata(); @@ -345,7 +388,13 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { Ok(()) } - fn write_internal(&mut self, address: u8, bytes: &[u8], send_stop: bool) -> Result<(), Error> { + fn write_internal( + &mut self, + address: u8, + bytes: &[u8], + send_stop: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { let completed_chunks = bytes.len() / 255; let total_chunks = if completed_chunks * 255 == bytes.len() { completed_chunks @@ -359,14 +408,20 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // ST SAD+W // NOTE(unsafe) We have &mut self unsafe { - Self::master_write(address, bytes.len().min(255), Stop::Software, last_chunk_idx != 0); + Self::master_write( + address, + bytes.len().min(255), + Stop::Software, + last_chunk_idx != 0, + &check_timeout, + )?; } for (number, chunk) in bytes.chunks(255).enumerate() { if number != 0 { // NOTE(unsafe) We have &mut self unsafe { - Self::master_continue(chunk.len(), number != last_chunk_idx); + Self::master_continue(chunk.len(), number != last_chunk_idx, &check_timeout)?; } } @@ -374,7 +429,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // Wait until we are allowed to send data // (START has been ACKed or last byte when // through) - self.wait_txe()?; + self.wait_txe(&check_timeout)?; unsafe { T::regs().txdr().write(|w| w.set_txdata(*byte)); @@ -382,7 +437,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { } } // Wait until the write finishes - self.wait_tc()?; + self.wait_tc(&check_timeout)?; if send_stop { self.master_stop(); @@ -396,6 +451,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { bytes: &[u8], first_slice: bool, last_slice: bool, + check_timeout: impl Fn() -> Result<(), Error>, ) -> Result<(), Error> where TXDMA: crate::i2c::TxDma, @@ -427,7 +483,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { state.chunks_transferred.store(0, Ordering::Relaxed); let mut remaining_len = total_len; - let _on_drop = OnDrop::new(|| { + let on_drop = OnDrop::new(|| { let regs = T::regs(); unsafe { regs.cr1().modify(|w| { @@ -447,11 +503,12 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { total_len.min(255), Stop::Software, (total_chunks != 1) || !last_slice, - ); + &check_timeout, + )?; } } else { unsafe { - Self::master_continue(total_len.min(255), (total_chunks != 1) || !last_slice); + Self::master_continue(total_len.min(255), (total_chunks != 1) || !last_slice, &check_timeout)?; T::regs().cr1().modify(|w| w.set_tcie(true)); } } @@ -461,32 +518,43 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed); if chunks_transferred == total_chunks { - return Poll::Ready(()); + return Poll::Ready(Ok(())); } else if chunks_transferred != 0 { remaining_len = remaining_len.saturating_sub(255); let last_piece = (chunks_transferred + 1 == total_chunks) && last_slice; // NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers unsafe { - Self::master_continue(remaining_len.min(255), !last_piece); + if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, &check_timeout) { + return Poll::Ready(Err(e)); + } T::regs().cr1().modify(|w| w.set_tcie(true)); } } Poll::Pending }) - .await; + .await?; dma_transfer.await; if last_slice { // This should be done already - self.wait_tc()?; + self.wait_tc(&check_timeout)?; self.master_stop(); } + + drop(on_drop); + Ok(()) } - async fn read_dma_internal(&mut self, address: u8, buffer: &mut [u8], restart: bool) -> Result<(), Error> + async fn read_dma_internal( + &mut self, + address: u8, + buffer: &mut [u8], + restart: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> where RXDMA: crate::i2c::RxDma, { @@ -515,7 +583,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { state.chunks_transferred.store(0, Ordering::Relaxed); let mut remaining_len = total_len; - let _on_drop = OnDrop::new(|| { + let on_drop = OnDrop::new(|| { let regs = T::regs(); unsafe { regs.cr1().modify(|w| { @@ -527,7 +595,14 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers unsafe { - Self::master_read(address, total_len.min(255), Stop::Software, total_chunks != 1, restart); + Self::master_read( + address, + total_len.min(255), + Stop::Software, + total_chunks != 1, + restart, + &check_timeout, + )?; } poll_fn(|cx| { @@ -535,26 +610,31 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed); if chunks_transferred == total_chunks { - return Poll::Ready(()); + return Poll::Ready(Ok(())); } else if chunks_transferred != 0 { remaining_len = remaining_len.saturating_sub(255); let last_piece = chunks_transferred + 1 == total_chunks; // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers unsafe { - Self::master_continue(remaining_len.min(255), !last_piece); + if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, &check_timeout) { + return Poll::Ready(Err(e)); + } T::regs().cr1().modify(|w| w.set_tcie(true)); } } Poll::Pending }) - .await; + .await?; dma_transfer.await; // This should be done already - self.wait_tc()?; + self.wait_tc(&check_timeout)?; self.master_stop(); + + drop(on_drop); + Ok(()) } @@ -566,9 +646,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { TXDMA: crate::i2c::TxDma, { if bytes.is_empty() { - self.write_internal(address, bytes, true) + self.write_internal(address, bytes, true, || Ok(())) } else { - self.write_dma_internal(address, bytes, true, true).await + self.write_dma_internal(address, bytes, true, true, || Ok(())).await } } @@ -587,7 +667,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { let next = iter.next(); let is_last = next.is_none(); - self.write_dma_internal(address, c, first, is_last).await?; + self.write_dma_internal(address, c, first, is_last, || Ok(())).await?; first = false; current = next; } @@ -599,9 +679,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { RXDMA: crate::i2c::RxDma, { if buffer.is_empty() { - self.read_internal(address, buffer, false) + self.read_internal(address, buffer, false, || Ok(())) } else { - self.read_dma_internal(address, buffer, false).await + self.read_dma_internal(address, buffer, false, || Ok(())).await } } @@ -611,15 +691,15 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { RXDMA: super::RxDma, { if bytes.is_empty() { - self.write_internal(address, bytes, false)?; + self.write_internal(address, bytes, false, || Ok(()))?; } else { - self.write_dma_internal(address, bytes, true, true).await?; + self.write_dma_internal(address, bytes, true, true, || Ok(())).await?; } if buffer.is_empty() { - self.read_internal(address, buffer, true)?; + self.read_internal(address, buffer, true, || Ok(()))?; } else { - self.read_dma_internal(address, buffer, true).await?; + self.read_dma_internal(address, buffer, true, || Ok(())).await?; } Ok(()) @@ -628,22 +708,55 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // ========================= // Blocking public API - pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.read_internal(address, buffer, false) + pub fn blocking_read_timeout( + &mut self, + address: u8, + buffer: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.read_internal(address, buffer, false, &check_timeout) // Automatic Stop } + pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(address, buffer, || Ok(())) + } + + pub fn blocking_write_timeout( + &mut self, + address: u8, + bytes: &[u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.write_internal(address, bytes, true, &check_timeout) + } + pub fn blocking_write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Error> { - self.write_internal(address, bytes, true) + self.blocking_write_timeout(address, bytes, || Ok(())) } - pub fn blocking_write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { - self.write_internal(address, bytes, false)?; - self.read_internal(address, buffer, true) + pub fn blocking_write_read_timeout( + &mut self, + address: u8, + bytes: &[u8], + buffer: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.write_internal(address, bytes, false, &check_timeout)?; + self.read_internal(address, buffer, true, &check_timeout) // Automatic Stop } - pub fn blocking_write_vectored(&mut self, address: u8, bytes: &[&[u8]]) -> Result<(), Error> { + pub fn blocking_write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_write_read_timeout(address, bytes, buffer, || Ok(())) + } + + pub fn blocking_write_vectored_timeout( + &mut self, + address: u8, + bytes: &[&[u8]], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { if bytes.is_empty() { return Err(Error::ZeroLengthTransfer); } @@ -657,7 +770,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { first_length.min(255), Stop::Software, (first_length > 255) || (last_slice_index != 0), - ); + &check_timeout, + )?; } for (idx, slice) in bytes.iter().enumerate() { @@ -673,7 +787,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { if idx != 0 { // NOTE(unsafe) We have &mut self unsafe { - Self::master_continue(slice_len.min(255), (idx != last_slice_index) || (slice_len > 255)); + Self::master_continue( + slice_len.min(255), + (idx != last_slice_index) || (slice_len > 255), + &check_timeout, + )?; } } @@ -681,7 +799,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { if number != 0 { // NOTE(unsafe) We have &mut self unsafe { - Self::master_continue(chunk.len(), (number != last_chunk_idx) || (idx != last_slice_index)); + Self::master_continue( + chunk.len(), + (number != last_chunk_idx) || (idx != last_slice_index), + &check_timeout, + )?; } } @@ -689,7 +811,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // Wait until we are allowed to send data // (START has been ACKed or last byte when // through) - self.wait_txe()?; + self.wait_txe(&check_timeout)?; // Put byte on the wire //self.i2c.txdr.write(|w| w.txdata().bits(*byte)); @@ -700,11 +822,15 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { } } // Wait until the write finishes - self.wait_tc()?; + self.wait_tc(&check_timeout)?; self.master_stop(); Ok(()) } + + pub fn blocking_write_vectored(&mut self, address: u8, bytes: &[&[u8]]) -> Result<(), Error> { + self.blocking_write_vectored_timeout(address, bytes, || Ok(())) + } } mod eh02 { @@ -929,43 +1055,35 @@ mod eh1 { #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eha { - use core::future::Future; - use super::super::{RxDma, TxDma}; use super::*; impl<'d, T: Instance, TXDMA: TxDma, RXDMA: RxDma> embedded_hal_async::i2c::I2c for I2c<'d, T, TXDMA, RXDMA> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(address, buffer) + async fn read<'a>(&'a mut self, address: u8, read: &'a mut [u8]) -> Result<(), Self::Error> { + self.read(address, read).await } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(address, bytes) + async fn write<'a>(&'a mut self, address: u8, write: &'a [u8]) -> Result<(), Self::Error> { + self.write(address, write).await } - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - fn write_read<'a>( + async fn write_read<'a>( &'a mut self, address: u8, - bytes: &'a [u8], - buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - self.write_read(address, bytes, buffer) + write: &'a [u8], + read: &'a mut [u8], + ) -> Result<(), Self::Error> { + self.write_read(address, write, read).await } - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( + async fn transaction<'a, 'b>( &'a mut self, address: u8, - operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { + operations: &'a mut [embedded_hal_1::i2c::Operation<'b>], + ) -> Result<(), Self::Error> { let _ = address; let _ = operations; - async move { todo!() } + todo!() } } } diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 906980ac4..93547a7d9 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -1,5 +1,9 @@ #![no_std] -#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] +#![cfg_attr( + feature = "nightly", + feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections) +)] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] // This must go FIRST so that all the other modules see its macros. pub mod fmt; @@ -54,9 +58,9 @@ pub mod sdmmc; pub mod spi; #[cfg(usart)] pub mod usart; -#[cfg(usb)] +#[cfg(all(usb, feature = "time"))] pub mod usb; -#[cfg(any(otgfs, otghs))] +#[cfg(otg)] pub mod usb_otg; #[cfg(iwdg)] @@ -77,6 +81,8 @@ pub(crate) mod _generated { // Reexports pub use _generated::{peripherals, Peripherals}; pub use embassy_cortex_m::executor; +#[cfg(any(dma, bdma))] +use embassy_cortex_m::interrupt::Priority; pub use embassy_cortex_m::interrupt::_export::interrupt; pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; #[cfg(feature = "unstable-pac")] @@ -89,6 +95,10 @@ pub struct Config { pub rcc: rcc::Config, #[cfg(dbgmcu)] pub enable_debug_during_sleep: bool, + #[cfg(bdma)] + pub bdma_interrupt_priority: Priority, + #[cfg(dma)] + pub dma_interrupt_priority: Priority, } impl Default for Config { @@ -97,6 +107,10 @@ impl Default for Config { rcc: Default::default(), #[cfg(dbgmcu)] enable_debug_during_sleep: true, + #[cfg(bdma)] + bdma_interrupt_priority: Priority::P0, + #[cfg(dma)] + dma_interrupt_priority: Priority::P0, } } } @@ -109,7 +123,7 @@ pub fn init(config: Config) -> Peripherals { #[cfg(dbgmcu)] if config.enable_debug_during_sleep { crate::pac::DBGMCU.cr().modify(|cr| { - #[cfg(any(dbgmcu_f0, dbgmcu_g0, dbgmcu_u5))] + #[cfg(any(dbgmcu_f0, dbgmcu_c0, dbgmcu_g0, dbgmcu_u5))] { cr.set_dbg_stop(true); cr.set_dbg_standby(true); @@ -135,7 +149,12 @@ pub fn init(config: Config) -> Peripherals { } gpio::init(); - dma::init(); + dma::init( + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + ); #[cfg(feature = "exti")] exti::init(); diff --git a/embassy-stm32/src/rcc/c0.rs b/embassy-stm32/src/rcc/c0.rs new file mode 100644 index 000000000..6c7b36647 --- /dev/null +++ b/embassy-stm32/src/rcc/c0.rs @@ -0,0 +1,233 @@ +use crate::pac::flash::vals::Latency; +use crate::pac::rcc::vals::{Hpre, Hsidiv, Ppre, Sw}; +use crate::pac::{FLASH, RCC}; +use crate::rcc::{set_freqs, Clocks}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(48_000_000); + +/// LSI speed +pub const LSI_FREQ: Hertz = Hertz(32_000); + +/// System clock mux source +#[derive(Clone, Copy)] +pub enum ClockSrc { + HSE(Hertz), + HSI(HSIPrescaler), + LSI, +} + +#[derive(Clone, Copy)] +pub enum HSIPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl Into for HSIPrescaler { + fn into(self) -> Hsidiv { + match self { + HSIPrescaler::NotDivided => Hsidiv::DIV1, + HSIPrescaler::Div2 => Hsidiv::DIV2, + HSIPrescaler::Div4 => Hsidiv::DIV4, + HSIPrescaler::Div8 => Hsidiv::DIV8, + HSIPrescaler::Div16 => Hsidiv::DIV16, + HSIPrescaler::Div32 => Hsidiv::DIV32, + HSIPrescaler::Div64 => Hsidiv::DIV64, + HSIPrescaler::Div128 => Hsidiv::DIV128, + } + } +} + +/// AHB prescaler +#[derive(Clone, Copy, PartialEq)] +pub enum AHBPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, + Div64, + Div128, + Div256, + Div512, +} + +/// APB prescaler +#[derive(Clone, Copy)] +pub enum APBPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, +} + +impl Into for APBPrescaler { + fn into(self) -> Ppre { + match self { + APBPrescaler::NotDivided => Ppre::DIV1, + APBPrescaler::Div2 => Ppre::DIV2, + APBPrescaler::Div4 => Ppre::DIV4, + APBPrescaler::Div8 => Ppre::DIV8, + APBPrescaler::Div16 => Ppre::DIV16, + } + } +} + +impl Into for AHBPrescaler { + fn into(self) -> Hpre { + match self { + AHBPrescaler::NotDivided => Hpre::DIV1, + AHBPrescaler::Div2 => Hpre::DIV2, + AHBPrescaler::Div4 => Hpre::DIV4, + AHBPrescaler::Div8 => Hpre::DIV8, + AHBPrescaler::Div16 => Hpre::DIV16, + AHBPrescaler::Div64 => Hpre::DIV64, + AHBPrescaler::Div128 => Hpre::DIV128, + AHBPrescaler::Div256 => Hpre::DIV256, + AHBPrescaler::Div512 => Hpre::DIV512, + } + } +} + +/// Clocks configutation +pub struct Config { + pub mux: ClockSrc, + pub ahb_pre: AHBPrescaler, + pub apb_pre: APBPrescaler, +} + +impl Default for Config { + #[inline] + fn default() -> Config { + Config { + mux: ClockSrc::HSI(HSIPrescaler::NotDivided), + ahb_pre: AHBPrescaler::NotDivided, + apb_pre: APBPrescaler::NotDivided, + } + } +} + +pub(crate) unsafe fn init(config: Config) { + let (sys_clk, sw) = match config.mux { + ClockSrc::HSI(div) => { + // Enable HSI + let div: Hsidiv = div.into(); + RCC.cr().write(|w| { + w.set_hsidiv(div); + w.set_hsion(true) + }); + while !RCC.cr().read().hsirdy() {} + + (HSI_FREQ.0 >> div.0, Sw::HSI) + } + ClockSrc::HSE(freq) => { + // Enable HSE + RCC.cr().write(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + + (freq.0, Sw::HSE) + } + ClockSrc::LSI => { + // Enable LSI + RCC.csr2().write(|w| w.set_lsion(true)); + while !RCC.csr2().read().lsirdy() {} + (LSI_FREQ.0, Sw::LSI) + } + }; + + // Determine the flash latency implied by the target clock speed + // RM0454 § 3.3.4: + let target_flash_latency = if sys_clk <= 24_000_000 { + Latency::WS0 + } else { + Latency::WS1 + }; + + // Increase the number of cycles we wait for flash if the new value is higher + // There's no harm in waiting a little too much before the clock change, but we'll + // crash immediately if we don't wait enough after the clock change + let mut set_flash_latency_after = false; + FLASH.acr().modify(|w| { + // Is the current flash latency less than what we need at the new SYSCLK? + if w.latency().0 <= target_flash_latency.0 { + // We must increase the number of wait states now + w.set_latency(target_flash_latency) + } else { + // We may decrease the number of wait states later + set_flash_latency_after = true; + } + + // RM0490 § 3.3.4: + // > Prefetch is enabled by setting the PRFTEN bit of the FLASH access control register + // > (FLASH_ACR). This feature is useful if at least one wait state is needed to access the + // > Flash memory. + // + // Enable flash prefetching if we have at least one wait state, and disable it otherwise. + w.set_prften(target_flash_latency.0 > 0); + }); + + if !set_flash_latency_after { + // Spin until the effective flash latency is compatible with the clock change + while FLASH.acr().read().latency().0 < target_flash_latency.0 {} + } + + // Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once + let (sw, hpre, ppre) = (sw.into(), config.ahb_pre.into(), config.apb_pre.into()); + RCC.cfgr().modify(|w| { + w.set_sw(sw); + w.set_hpre(hpre); + w.set_ppre(ppre); + }); + + if set_flash_latency_after { + // We can make the flash require fewer wait states + // Spin until the SYSCLK changes have taken effect + loop { + let cfgr = RCC.cfgr().read(); + if cfgr.sw() == sw && cfgr.hpre() == hpre && cfgr.ppre() == ppre { + break; + } + } + + // Set the flash latency to require fewer wait states + FLASH.acr().modify(|w| w.set_latency(target_flash_latency)); + } + + let ahb_div = match config.ahb_pre { + AHBPrescaler::NotDivided => 1, + AHBPrescaler::Div2 => 2, + AHBPrescaler::Div4 => 4, + AHBPrescaler::Div8 => 8, + AHBPrescaler::Div16 => 16, + AHBPrescaler::Div64 => 64, + AHBPrescaler::Div128 => 128, + AHBPrescaler::Div256 => 256, + AHBPrescaler::Div512 => 512, + }; + let ahb_freq = sys_clk / ahb_div; + + let (apb_freq, apb_tim_freq) = match config.apb_pre { + APBPrescaler::NotDivided => (ahb_freq, ahb_freq), + pre => { + let pre: Ppre = pre.into(); + let pre: u8 = 1 << (pre.0 - 3); + let freq = ahb_freq / pre as u32; + (freq, freq * 2) + } + }; + + set_freqs(Clocks { + sys: Hertz(sys_clk), + ahb1: Hertz(ahb_freq), + apb1: Hertz(apb_freq), + apb1_tim: Hertz(apb_tim_freq), + }); +} diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 1b1180c03..d4bd3d6b8 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -10,6 +10,7 @@ use crate::time::Hertz; #[cfg_attr(rcc_f3, path = "f3.rs")] #[cfg_attr(any(rcc_f4, rcc_f410), path = "f4.rs")] #[cfg_attr(rcc_f7, path = "f7.rs")] +#[cfg_attr(rcc_c0, path = "c0.rs")] #[cfg_attr(rcc_g0, path = "g0.rs")] #[cfg_attr(rcc_g4, path = "g4.rs")] #[cfg_attr(any(rcc_h7, rcc_h7ab), path = "h7.rs")] @@ -23,16 +24,17 @@ use crate::time::Hertz; mod _version; pub use _version::*; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Clocks { pub sys: Hertz, // APB pub apb1: Hertz, pub apb1_tim: Hertz, - #[cfg(not(rcc_g0))] + #[cfg(not(any(rcc_c0, rcc_g0)))] pub apb2: Hertz, - #[cfg(not(rcc_g0))] + #[cfg(not(any(rcc_c0, rcc_g0)))] pub apb2_tim: Hertz, #[cfg(any(rcc_wl5, rcc_wle, rcc_u5))] pub apb3: Hertz, @@ -71,6 +73,7 @@ static mut CLOCK_FREQS: MaybeUninit = MaybeUninit::uninit(); /// /// Safety: Sets a mutable global. pub(crate) unsafe fn set_freqs(freqs: Clocks) { + debug!("rcc: {:?}", freqs); CLOCK_FREQS.as_mut_ptr().write(freqs); } diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index 960c45322..2ba339ecf 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -295,6 +295,7 @@ pub struct Config { pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, pub apb3_pre: APBPrescaler, + pub hsi48: bool, } impl Default for Config { @@ -305,6 +306,7 @@ impl Default for Config { apb1_pre: Default::default(), apb2_pre: Default::default(), apb3_pre: Default::default(), + hsi48: false, } } } @@ -320,7 +322,6 @@ pub(crate) unsafe fn init(config: Config) { RCC.cr().write(|w| { w.set_msipllen(false); w.set_msison(true); - w.set_msison(true); }); while !RCC.cr().read().msisrdy() {} @@ -340,9 +341,20 @@ pub(crate) unsafe fn init(config: Config) { } ClockSrc::PLL1R(src, m, n, div) => { let freq = match src { - PllSrc::MSI(_) => MSIRange::default().into(), - PllSrc::HSE(hertz) => hertz.0, - PllSrc::HSI16 => HSI_FREQ.0, + PllSrc::MSI(_) => { + // TODO: enable MSI + MSIRange::default().into() + } + PllSrc::HSE(hertz) => { + // TODO: enable HSE + hertz.0 + } + PllSrc::HSI16 => { + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + HSI_FREQ.0 + } }; // disable @@ -355,6 +367,7 @@ pub(crate) unsafe fn init(config: Config) { RCC.pll1cfgr().write(|w| { w.set_pllm(m.into()); w.set_pllsrc(src.into()); + w.set_pllren(true); }); RCC.pll1divr().modify(|w| { @@ -365,15 +378,16 @@ pub(crate) unsafe fn init(config: Config) { // Enable PLL RCC.cr().modify(|w| w.set_pllon(0, true)); while !RCC.cr().read().pllrdy(0) {} - RCC.pll1cfgr().modify(|w| w.set_pllren(true)); - - RCC.cr().write(|w| w.set_pllon(0, true)); - while !RCC.cr().read().pllrdy(0) {} pll_ck } }; + if config.hsi48 { + RCC.cr().modify(|w| w.set_hsi48on(true)); + while !RCC.cr().read().hsi48rdy() {} + } + // TODO make configurable let power_vos = VoltageScale::Range4; diff --git a/embassy-stm32/src/rcc/wb.rs b/embassy-stm32/src/rcc/wb.rs index f6a5feea6..c9ada83e2 100644 --- a/embassy-stm32/src/rcc/wb.rs +++ b/embassy-stm32/src/rcc/wb.rs @@ -64,7 +64,7 @@ impl Into for APBPrescaler { impl Into for AHBPrescaler { fn into(self) -> u8 { match self { - AHBPrescaler::NotDivided => 1, + AHBPrescaler::NotDivided => 0x0, AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div4 => 0x09, diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs index 347674918..82b0d04e2 100644 --- a/embassy-stm32/src/rcc/wl.rs +++ b/embassy-stm32/src/rcc/wl.rs @@ -158,7 +158,7 @@ impl Into for APBPrescaler { impl Into for AHBPrescaler { fn into(self) -> u8 { match self { - AHBPrescaler::NotDivided => 1, + AHBPrescaler::NotDivided => 0x0, AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div4 => 0x09, diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index 10fc4a75e..1e16b8478 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -32,6 +32,11 @@ impl<'d, T: Instance> Rng<'d, T> { } pub fn reset(&mut self) { + // rng_v2 locks up on seed error, needs reset + #[cfg(rng_v2)] + if unsafe { T::regs().sr().read().seis() } { + T::reset(); + } unsafe { T::regs().cr().modify(|reg| { reg.set_rngen(true); @@ -90,8 +95,10 @@ impl<'d, T: Instance> Rng<'d, T> { impl<'d, T: Instance> RngCore for Rng<'d, T> { fn next_u32(&mut self) -> u32 { loop { - let bits = unsafe { T::regs().sr().read() }; - if bits.drdy() { + let sr = unsafe { T::regs().sr().read() }; + if sr.seis() | sr.ceis() { + self.reset(); + } else if sr.drdy() { return unsafe { T::regs().dr().read() }; } } diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index a8bc6385f..3f07e0c07 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -18,6 +18,9 @@ use crate::rcc::RccPeripheral; use crate::time::Hertz; use crate::{peripherals, Peripheral}; +/// Frequency used for SD Card initialization. Must be no higher than 400 kHz. +const SD_INIT_FREQ: Hertz = Hertz(400_000); + /// The signalling scheme used on the SDMMC bus #[non_exhaustive] #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -295,7 +298,7 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { T::reset(); let inner = T::inner(); - let clock = unsafe { inner.new_inner(T::frequency()) }; + unsafe { inner.new_inner() }; irq.set_handler(Self::on_interrupt); irq.unpend(); @@ -314,7 +317,7 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { d3, config, - clock, + clock: SD_INIT_FREQ, signalling: Default::default(), card: None, } @@ -415,7 +418,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { T::reset(); let inner = T::inner(); - let clock = unsafe { inner.new_inner(T::frequency()) }; + unsafe { inner.new_inner() }; irq.set_handler(Self::on_interrupt); irq.unpend(); @@ -434,7 +437,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { d3, config, - clock, + clock: SD_INIT_FREQ, signalling: Default::default(), card: None, } @@ -561,16 +564,10 @@ impl SdmmcInner { /// # Safety /// /// Access to `regs` registers should be exclusive - unsafe fn new_inner(&self, kernel_clk: Hertz) -> Hertz { + unsafe fn new_inner(&self) { let regs = self.0; - // While the SD/SDIO card or eMMC is in identification mode, - // the SDMMC_CK frequency must be less than 400 kHz. - let (clkdiv, clock) = unwrap!(clk_div(kernel_clk, 400_000)); - regs.clkcr().write(|w| { - w.set_widbus(0); - w.set_clkdiv(clkdiv); w.set_pwrsav(false); w.set_negedge(false); w.set_hwfc_en(true); @@ -582,8 +579,6 @@ impl SdmmcInner { // Power off, writen 00: Clock to the card is stopped; // D[7:0], CMD, and CK are driven high. regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::Off as u8)); - - clock } /// Initializes card (if present) and sets the bus at the @@ -605,6 +600,19 @@ impl SdmmcInner { // NOTE(unsafe) We have exclusive access to the peripheral unsafe { + // While the SD/SDIO card or eMMC is in identification mode, + // the SDMMC_CK frequency must be no more than 400 kHz. + let (clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); + *clock = init_clock; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + self.wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(0); + w.set_clkdiv(clkdiv); + }); + regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); self.cmd(Cmd::idle(), false)?; @@ -1524,7 +1532,7 @@ foreach_peripheral!( }; ); -#[cfg(feature = "sdmmc-rs")] +#[cfg(feature = "embedded-sdmmc")] mod sdmmc_rs { use core::future::Future; @@ -1532,16 +1540,16 @@ mod sdmmc_rs { use super::*; - impl<'d, T: Instance, P: Pins> BlockDevice for Sdmmc<'d, T, P> { + impl<'d, T: Instance, Dma: SdmmcDma> BlockDevice for Sdmmc<'d, T, Dma> { type Error = Error; - type ReadFuture<'a> + + type ReadFuture<'a> = impl Future> + 'a where - Self: 'a, - = impl Future> + 'a; - type WriteFuture<'a> + Self: 'a; + + type WriteFuture<'a> = impl Future> + 'a where - Self: 'a, - = impl Future> + 'a; + Self: 'a; fn read<'a>( &'a mut self, @@ -1550,19 +1558,14 @@ mod sdmmc_rs { _reason: &str, ) -> Self::ReadFuture<'a> { async move { - let card_capacity = self.card()?.card_type; - let inner = T::inner(); - let state = T::state(); let mut address = start_block_idx.0; for block in blocks.iter_mut() { let block: &mut [u8; 512] = &mut block.contents; // NOTE(unsafe) Block uses align(4) - let buf = unsafe { &mut *(block as *mut [u8; 512] as *mut [u32; 128]) }; - inner - .read_block(address, buf, card_capacity, state, self.config.data_transfer_timeout) - .await?; + let block = unsafe { &mut *(block as *mut _ as *mut DataBlock) }; + self.read_block(address, block).await?; address += 1; } Ok(()) @@ -1571,19 +1574,14 @@ mod sdmmc_rs { fn write<'a>(&'a mut self, blocks: &'a [Block], start_block_idx: BlockIdx) -> Self::WriteFuture<'a> { async move { - let card = self.card.as_mut().ok_or(Error::NoCard)?; - let inner = T::inner(); - let state = T::state(); let mut address = start_block_idx.0; for block in blocks.iter() { let block: &[u8; 512] = &block.contents; // NOTE(unsafe) DataBlock uses align 4 - let buf = unsafe { &*(block as *const [u8; 512] as *const [u32; 128]) }; - inner - .write_block(address, buf, card, state, self.config.data_transfer_timeout) - .await?; + let block = unsafe { &*(block as *const _ as *const DataBlock) }; + self.write_block(address, block).await?; address += 1; } Ok(()) diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 396427782..e5ba746e4 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -8,9 +8,9 @@ use embassy_hal_common::{into_ref, PeripheralRef}; pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; use self::sealed::WordSize; -use crate::dma::{slice_ptr_parts, NoDma, Transfer}; +use crate::dma::{slice_ptr_parts, Transfer}; use crate::gpio::sealed::{AFType, Pin as _}; -use crate::gpio::AnyPin; +use crate::gpio::{AnyPin, Pull}; use crate::pac::spi::{regs, vals, Spi as Regs}; use crate::rcc::RccPeripheral; use crate::time::Hertz; @@ -93,15 +93,18 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { config: Config, ) -> Self { into_ref!(peri, sck, mosi, miso); + + let sck_pull_mode = match config.mode.polarity { + Polarity::IdleLow => Pull::Down, + Polarity::IdleHigh => Pull::Up, + }; + unsafe { - sck.set_as_af(sck.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] + sck.set_as_af_pull(sck.af_num(), AFType::OutputPushPull, sck_pull_mode); sck.set_speed(crate::gpio::Speed::VeryHigh); mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] mosi.set_speed(crate::gpio::Speed::VeryHigh); miso.set_as_af(miso.af_num(), AFType::Input); - #[cfg(any(spi_v2, spi_v3, spi_v4))] miso.set_speed(crate::gpio::Speed::VeryHigh); } @@ -129,10 +132,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { into_ref!(sck, miso); unsafe { sck.set_as_af(sck.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] sck.set_speed(crate::gpio::Speed::VeryHigh); miso.set_as_af(miso.af_num(), AFType::Input); - #[cfg(any(spi_v2, spi_v3, spi_v4))] miso.set_speed(crate::gpio::Speed::VeryHigh); } @@ -160,10 +161,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { into_ref!(sck, mosi); unsafe { sck.set_as_af(sck.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] sck.set_speed(crate::gpio::Speed::VeryHigh); mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] mosi.set_speed(crate::gpio::Speed::VeryHigh); } @@ -474,7 +473,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { let tx_request = self.txdma.request(); let tx_dst = T::REGS.tx_ptr(); let clock_byte = 0x00u8; - let tx_f = crate::dma::write_repeated(&mut self.txdma, tx_request, clock_byte, clock_byte_count, tx_dst); + let tx_f = crate::dma::write_repeated(&mut self.txdma, tx_request, &clock_byte, clock_byte_count, tx_dst); unsafe { set_txdmaen(T::REGS, true); @@ -772,10 +771,13 @@ fn finish_dma(regs: Regs) { #[cfg(not(any(spi_v3, spi_v4)))] while regs.sr().read().bsy() {} + // Disable the spi peripheral regs.cr1().modify(|w| { w.set_spe(false); }); + // The peripheral automatically disables the DMA stream on completion without error, + // but it does not clear the RXDMAEN/TXDMAEN flag in CR2. #[cfg(not(any(spi_v3, spi_v4)))] regs.cr2().modify(|reg| { reg.set_txdmaen(false); @@ -812,7 +814,7 @@ mod eh02 { // some marker traits. For details, see https://github.com/rust-embedded/embedded-hal/pull/289 macro_rules! impl_blocking { ($w:ident) => { - impl<'d, T: Instance> embedded_hal_02::blocking::spi::Write<$w> for Spi<'d, T, NoDma, NoDma> { + impl<'d, T: Instance, Tx, Rx> embedded_hal_02::blocking::spi::Write<$w> for Spi<'d, T, Tx, Rx> { type Error = Error; fn write(&mut self, words: &[$w]) -> Result<(), Self::Error> { @@ -820,7 +822,7 @@ mod eh02 { } } - impl<'d, T: Instance> embedded_hal_02::blocking::spi::Transfer<$w> for Spi<'d, T, NoDma, NoDma> { + impl<'d, T: Instance, Tx, Rx> embedded_hal_02::blocking::spi::Transfer<$w> for Spi<'d, T, Tx, Rx> { type Error = Error; fn transfer<'w>(&mut self, words: &'w mut [$w]) -> Result<&'w [$w], Self::Error> { @@ -849,19 +851,19 @@ mod eh1 { } } - impl<'d, T: Instance, W: Word> embedded_hal_1::spi::SpiBusRead for Spi<'d, T, NoDma, NoDma> { + impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBusRead for Spi<'d, T, Tx, Rx> { fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> { self.blocking_read(words) } } - impl<'d, T: Instance, W: Word> embedded_hal_1::spi::SpiBusWrite for Spi<'d, T, NoDma, NoDma> { + impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBusWrite for Spi<'d, T, Tx, Rx> { fn write(&mut self, words: &[W]) -> Result<(), Self::Error> { self.blocking_write(words) } } - impl<'d, T: Instance, W: Word> embedded_hal_1::spi::SpiBus for Spi<'d, T, NoDma, NoDma> { + impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBus for Spi<'d, T, Tx, Rx> { fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> { self.blocking_transfer(read, write) } @@ -885,46 +887,34 @@ mod eh1 { #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eha { - use core::future::Future; - use super::*; impl<'d, T: Instance, Tx, Rx> embedded_hal_async::spi::SpiBusFlush for Spi<'d, T, Tx, Rx> { - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async { Ok(()) } + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } impl<'d, T: Instance, Tx: TxDma, Rx, W: Word> embedded_hal_async::spi::SpiBusWrite for Spi<'d, T, Tx, Rx> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, data: &'a [W]) -> Self::WriteFuture<'a> { - self.write(data) + async fn write(&mut self, words: &[W]) -> Result<(), Self::Error> { + self.write(words).await } } impl<'d, T: Instance, Tx: TxDma, Rx: RxDma, W: Word> embedded_hal_async::spi::SpiBusRead for Spi<'d, T, Tx, Rx> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, data: &'a mut [W]) -> Self::ReadFuture<'a> { - self.read(data) + async fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + self.read(words).await } } impl<'d, T: Instance, Tx: TxDma, Rx: RxDma, W: Word> embedded_hal_async::spi::SpiBus for Spi<'d, T, Tx, Rx> { - type TransferFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer<'a>(&'a mut self, rx: &'a mut [W], tx: &'a [W]) -> Self::TransferFuture<'a> { - self.transfer(rx, tx) + async fn transfer<'a>(&'a mut self, read: &'a mut [W], write: &'a [W]) -> Result<(), Self::Error> { + self.transfer(read, write).await } - type TransferInPlaceFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer_in_place<'a>(&'a mut self, words: &'a mut [W]) -> Self::TransferInPlaceFuture<'a> { - self.transfer_in_place(words) + async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [W]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await } } } diff --git a/embassy-stm32/src/subghz/timeout.rs b/embassy-stm32/src/subghz/timeout.rs index 28b3b0c21..0ae49dd90 100644 --- a/embassy-stm32/src/subghz/timeout.rs +++ b/embassy-stm32/src/subghz/timeout.rs @@ -439,6 +439,7 @@ impl From for [u8; 3] { } } +#[cfg(feature = "time")] impl From for embassy_time::Duration { fn from(to: Timeout) -> Self { embassy_time::Duration::from_micros(to.as_micros().into()) diff --git a/embassy-stm32/src/subghz/tx_params.rs b/embassy-stm32/src/subghz/tx_params.rs index cede6f2c1..03bdb1ea8 100644 --- a/embassy-stm32/src/subghz/tx_params.rs +++ b/embassy-stm32/src/subghz/tx_params.rs @@ -44,6 +44,7 @@ impl From for core::time::Duration { } } +#[cfg(feature = "time")] impl From for embassy_time::Duration { fn from(rt: RampTime) -> Self { match rt { diff --git a/embassy-stm32/src/time.rs b/embassy-stm32/src/time.rs index 49140bbe0..975517a48 100644 --- a/embassy-stm32/src/time.rs +++ b/embassy-stm32/src/time.rs @@ -2,6 +2,7 @@ /// Hertz #[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Hertz(pub u32); impl Hertz { diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index ed3225c51..8e84570a4 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -292,19 +292,23 @@ impl Driver for RtcDriver { }) } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { critical_section::with(|cs| { let r = T::regs_gp16(); - let n = alarm.id() as _; + let n = alarm.id() as usize; let alarm = self.get_alarm(cs, alarm); alarm.timestamp.set(timestamp); let t = self.now(); if timestamp <= t { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. unsafe { r.dier().modify(|w| w.set_ccie(n + 1, false)) }; - self.trigger_alarm(n, cs); - return; + + alarm.timestamp.set(u64::MAX); + + return false; } let safe_timestamp = timestamp.max(t + 3); @@ -317,6 +321,8 @@ impl Driver for RtcDriver { let diff = timestamp - t; // NOTE(unsafe) We're in a critical section unsafe { r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000)) }; + + true }) } } diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 2a711bc06..a27fcc1ca 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -1,8 +1,8 @@ use core::cell::RefCell; -use core::future::{poll_fn, Future}; +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; -use atomic_polyfill::{compiler_fence, Ordering}; use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; use embassy_hal_common::ring_buffer::RingBuffer; use embassy_sync::waitqueue::WakerRegistration; @@ -46,16 +46,102 @@ impl<'d, T: BasicInstance> Unpin for BufferedUart<'d, T> {} impl<'d, T: BasicInstance> BufferedUart<'d, T> { pub fn new( state: &'d mut State<'d, T>, - _uart: Uart<'d, T, NoDma, NoDma>, + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, irq: impl Peripheral

+ 'd, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], + config: Config, ) -> BufferedUart<'d, T> { - into_ref!(irq); + T::enable(); + T::reset(); + + Self::new_inner(state, peri, rx, tx, irq, tx_buffer, rx_buffer, config) + } + + pub fn new_with_rtscts( + state: &'d mut State<'d, T>, + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> BufferedUart<'d, T> { + into_ref!(cts, rts); + + T::enable(); + T::reset(); + + unsafe { + rts.set_as_af(rts.af_num(), AFType::OutputPushPull); + cts.set_as_af(cts.af_num(), AFType::Input); + T::regs().cr3().write(|w| { + w.set_rtse(true); + w.set_ctse(true); + }); + } + + Self::new_inner(state, peri, rx, tx, irq, tx_buffer, rx_buffer, config) + } + + #[cfg(not(usart_v1))] + pub fn new_with_de( + state: &'d mut State<'d, T>, + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, + de: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> BufferedUart<'d, T> { + into_ref!(de); + + T::enable(); + T::reset(); + + unsafe { + de.set_as_af(de.af_num(), AFType::OutputPushPull); + T::regs().cr3().write(|w| { + w.set_dem(true); + }); + } + + Self::new_inner(state, peri, rx, tx, irq, tx_buffer, rx_buffer, config) + } + + fn new_inner( + state: &'d mut State<'d, T>, + _peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> BufferedUart<'d, T> { + into_ref!(_peri, rx, tx, irq); let r = T::regs(); + + unsafe { + rx.set_as_af(rx.af_num(), AFType::Input); + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + } + + configure(r, &config, T::frequency(), T::MULTIPLIER, true, true); + unsafe { r.cr1().modify(|w| { + #[cfg(lpuart_v2)] + w.set_fifoen(true); + w.set_rxneie(true); w.set_idleie(true); }); @@ -283,32 +369,20 @@ impl<'u, 'd, T: BasicInstance> embedded_io::Io for BufferedUartTx<'u, 'd, T> { } impl<'d, T: BasicInstance> embedded_io::asynch::Read for BufferedUart<'d, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.inner_read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner_read(buf).await } } impl<'u, 'd, T: BasicInstance> embedded_io::asynch::Read for BufferedUartRx<'u, 'd, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.inner.inner_read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.inner_read(buf).await } } impl<'d, T: BasicInstance> embedded_io::asynch::BufRead for BufferedUart<'d, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; - - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - self.inner_fill_buf() + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner_fill_buf().await } fn consume(&mut self, amt: usize) { @@ -317,12 +391,8 @@ impl<'d, T: BasicInstance> embedded_io::asynch::BufRead for BufferedUart<'d, T> } impl<'u, 'd, T: BasicInstance> embedded_io::asynch::BufRead for BufferedUartRx<'u, 'd, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; - - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - self.inner.inner_fill_buf() + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.inner_fill_buf().await } fn consume(&mut self, amt: usize) { @@ -331,37 +401,21 @@ impl<'u, 'd, T: BasicInstance> embedded_io::asynch::BufRead for BufferedUartRx<' } impl<'d, T: BasicInstance> embedded_io::asynch::Write for BufferedUart<'d, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.inner_write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner_write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.inner_flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner_flush().await } } impl<'u, 'd, T: BasicInstance> embedded_io::asynch::Write for BufferedUartTx<'u, 'd, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.inner.inner_write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner.inner_write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.inner.inner_flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.inner_flush().await } } diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index a152a0c15..f80323e37 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -1,7 +1,13 @@ #![macro_use] +use core::future::poll_fn; use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; +use embassy_cortex_m::interrupt::InterruptExt; +use embassy_futures::select::{select, Either}; +use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use crate::dma::NoDma; @@ -10,6 +16,7 @@ use crate::gpio::sealed::AFType; use crate::pac::lpuart::{regs, vals, Lpuart as Regs}; #[cfg(not(any(lpuart_v1, lpuart_v2)))] use crate::pac::usart::{regs, vals, Usart as Regs}; +use crate::time::Hertz; use crate::{peripherals, Peripheral}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -44,6 +51,10 @@ pub struct Config { pub data_bits: DataBits, pub stop_bits: StopBits, pub parity: Parity, + /// if true, on read-like method, if there is a latent error pending, + /// read will abort, the error reported and cleared + /// if false, the error is ignored and cleared + pub detect_previous_overrun: bool, } impl Default for Config { @@ -53,6 +64,8 @@ impl Default for Config { data_bits: DataBits::DataBits8, stop_bits: StopBits::STOP1, parity: Parity::ParityNone, + // historical behavior + detect_previous_overrun: false, } } } @@ -70,10 +83,18 @@ pub enum Error { Overrun, /// Parity check error Parity, + /// Buffer too large for DMA + BufferTooLong, +} + +enum ReadCompletionEvent { + // DMA Read transfer completed first + DmaCompleted, + // Idle line detected first + Idle, } pub struct Uart<'d, T: BasicInstance, TxDma = NoDma, RxDma = NoDma> { - phantom: PhantomData<&'d mut T>, tx: UartTx<'d, T, TxDma>, rx: UartRx<'d, T, RxDma>, } @@ -84,12 +105,65 @@ pub struct UartTx<'d, T: BasicInstance, TxDma = NoDma> { } pub struct UartRx<'d, T: BasicInstance, RxDma = NoDma> { - phantom: PhantomData<&'d mut T>, + _peri: PeripheralRef<'d, T>, rx_dma: PeripheralRef<'d, RxDma>, + detect_previous_overrun: bool, } impl<'d, T: BasicInstance, TxDma> UartTx<'d, T, TxDma> { - fn new(tx_dma: PeripheralRef<'d, TxDma>) -> Self { + /// usefull if you only want Uart Tx. It saves 1 pin and consumes a little less power + pub fn new( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + T::enable(); + T::reset(); + + Self::new_inner(peri, tx, tx_dma, config) + } + + pub fn new_with_cts( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cts); + + T::enable(); + T::reset(); + + unsafe { + cts.set_as_af(cts.af_num(), AFType::Input); + T::regs().cr3().write(|w| { + w.set_ctse(true); + }); + } + Self::new_inner(peri, tx, tx_dma, config) + } + + fn new_inner( + _peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(_peri, tx, tx_dma); + + let r = T::regs(); + + unsafe { + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + } + + configure(r, &config, T::frequency(), T::MULTIPLIER, false, true); + + // create state once! + let _s = T::state(); + Self { tx_dma, phantom: PhantomData, @@ -135,10 +209,120 @@ impl<'d, T: BasicInstance, TxDma> UartTx<'d, T, TxDma> { } impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { - fn new(rx_dma: PeripheralRef<'d, RxDma>) -> Self { + /// usefull if you only want Uart Rx. It saves 1 pin and consumes a little less power + pub fn new( + peri: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + T::enable(); + T::reset(); + + Self::new_inner(peri, irq, rx, rx_dma, config) + } + + pub fn new_with_rts( + peri: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(rts); + + T::enable(); + T::reset(); + + unsafe { + rts.set_as_af(rts.af_num(), AFType::OutputPushPull); + T::regs().cr3().write(|w| { + w.set_rtse(true); + }); + } + + Self::new_inner(peri, irq, rx, rx_dma, config) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(peri, irq, rx, rx_dma); + + let r = T::regs(); + + unsafe { + rx.set_as_af(rx.af_num(), AFType::Input); + } + + configure(r, &config, T::frequency(), T::MULTIPLIER, true, false); + + irq.set_handler(Self::on_interrupt); + irq.unpend(); + irq.enable(); + + // create state once! + let _s = T::state(); + Self { + _peri: peri, rx_dma, - phantom: PhantomData, + detect_previous_overrun: config.detect_previous_overrun, + } + } + + fn on_interrupt(_: *mut ()) { + let r = T::regs(); + let s = T::state(); + + let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) }; + + let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie()); + + if has_errors { + // clear all interrupts and DMA Rx Request + unsafe { + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // disable parity interrupt + w.set_peie(false); + // disable idle line interrupt + w.set_idleie(false); + }); + r.cr3().modify(|w| { + // disable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(false); + // disable DMA Rx Request + w.set_dmar(false); + }); + } + + compiler_fence(Ordering::SeqCst); + + s.rx_waker.wake(); + } else if cr1.idleie() && sr.idle() { + // IDLE detected: no more data will come + unsafe { + r.cr1().modify(|w| { + // disable idle line detection + w.set_idleie(false); + }); + + r.cr3().modify(|w| { + // disable DMA Rx Request + w.set_dmar(false); + }); + } + compiler_fence(Ordering::SeqCst); + + s.rx_waker.wake(); } } @@ -146,17 +330,8 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { where RxDma: crate::usart::RxDma, { - let ch = &mut self.rx_dma; - let request = ch.request(); - unsafe { - T::regs().cr3().modify(|reg| { - reg.set_dmar(true); - }); - } - // If we don't assign future to a variable, the data register pointer - // is held across an await and makes the future non-Send. - let transfer = crate::dma::read(ch, request, rdr(T::regs()), buffer); - transfer.await; + self.inner_read(buffer, false).await?; + Ok(()) } @@ -211,57 +386,332 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { } Ok(()) } + + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result + where + RxDma: crate::usart::RxDma, + { + self.inner_read(buffer, true).await + } + + async fn inner_read_run( + &mut self, + buffer: &mut [u8], + enable_idle_line_detection: bool, + ) -> Result + where + RxDma: crate::usart::RxDma, + { + let r = T::regs(); + + // make sure USART state is restored to neutral state when this future is dropped + let on_drop = OnDrop::new(move || { + // defmt::trace!("Clear all USART interrupts and DMA Read Request"); + // clear all interrupts and DMA Rx Request + // SAFETY: only clears Rx related flags + unsafe { + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // disable parity interrupt + w.set_peie(false); + // disable idle line interrupt + w.set_idleie(false); + }); + r.cr3().modify(|w| { + // disable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(false); + // disable DMA Rx Request + w.set_dmar(false); + }); + } + }); + + let ch = &mut self.rx_dma; + let request = ch.request(); + + // Start USART DMA + // will not do anything yet because DMAR is not yet set + // future which will complete when DMA Read request completes + let transfer = crate::dma::read(ch, request, rdr(T::regs()), buffer); + + // SAFETY: The only way we might have a problem is using split rx and tx + // here we only modify or read Rx related flags, interrupts and DMA channel + unsafe { + // clear ORE flag just before enabling DMA Rx Request: can be mandatory for the second transfer + if !self.detect_previous_overrun { + let sr = sr(r).read(); + // This read also clears the error and idle interrupt flags on v1. + rdr(r).read_volatile(); + clear_interrupt_flags(r, sr); + } + + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // enable parity interrupt if not ParityNone + w.set_peie(w.pce()); + }); + + r.cr3().modify(|w| { + // enable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(true); + // enable DMA Rx Request + w.set_dmar(true); + }); + + compiler_fence(Ordering::SeqCst); + + // In case of errors already pending when reception started, interrupts may have already been raised + // and lead to reception abortion (Overrun error for instance). In such a case, all interrupts + // have been disabled in interrupt handler and DMA Rx Request has been disabled. + + let cr3 = r.cr3().read(); + + if !cr3.dmar() { + // something went wrong + // because the only way to get this flag cleared is to have an interrupt + + // DMA will be stopped when transfer is dropped + + let sr = sr(r).read(); + // This read also clears the error and idle interrupt flags on v1. + rdr(r).read_volatile(); + clear_interrupt_flags(r, sr); + + if sr.pe() { + return Err(Error::Parity); + } + if sr.fe() { + return Err(Error::Framing); + } + if sr.ne() { + return Err(Error::Noise); + } + if sr.ore() { + return Err(Error::Overrun); + } + + unreachable!(); + } + + if !enable_idle_line_detection { + transfer.await; + + return Ok(ReadCompletionEvent::DmaCompleted); + } + + // clear idle flag + let sr = sr(r).read(); + // This read also clears the error and idle interrupt flags on v1. + rdr(r).read_volatile(); + clear_interrupt_flags(r, sr); + + // enable idle interrupt + r.cr1().modify(|w| { + w.set_idleie(true); + }); + } + + compiler_fence(Ordering::SeqCst); + + // future which completes when idle line is detected + let idle = poll_fn(move |cx| { + let s = T::state(); + + s.rx_waker.register(cx.waker()); + + // SAFETY: read only and we only use Rx related flags + let sr = unsafe { sr(r).read() }; + + // SAFETY: only clears Rx related flags + unsafe { + // This read also clears the error and idle interrupt flags on v1. + rdr(r).read_volatile(); + clear_interrupt_flags(r, sr); + } + + compiler_fence(Ordering::SeqCst); + + let has_errors = sr.pe() || sr.fe() || sr.ne() || sr.ore(); + + if has_errors { + // all Rx interrupts and Rx DMA Request have already been cleared in interrupt handler + + if sr.pe() { + return Poll::Ready(Err(Error::Parity)); + } + if sr.fe() { + return Poll::Ready(Err(Error::Framing)); + } + if sr.ne() { + return Poll::Ready(Err(Error::Noise)); + } + if sr.ore() { + return Poll::Ready(Err(Error::Overrun)); + } + } + + if sr.idle() { + // Idle line detected + return Poll::Ready(Ok(())); + } + + Poll::Pending + }); + + // wait for the first of DMA request or idle line detected to completes + // select consumes its arguments + // when transfer is dropped, it will stop the DMA request + let r = match select(transfer, idle).await { + // DMA transfer completed first + Either::First(()) => Ok(ReadCompletionEvent::DmaCompleted), + + // Idle line detected first + Either::Second(Ok(())) => Ok(ReadCompletionEvent::Idle), + + // error occurred + Either::Second(Err(e)) => Err(e), + }; + + drop(on_drop); + + r + } + + async fn inner_read(&mut self, buffer: &mut [u8], enable_idle_line_detection: bool) -> Result + where + RxDma: crate::usart::RxDma, + { + if buffer.is_empty() { + return Ok(0); + } else if buffer.len() > 0xFFFF { + return Err(Error::BufferTooLong); + } + + let buffer_len = buffer.len(); + + // wait for DMA to complete or IDLE line detection if requested + let res = self.inner_read_run(buffer, enable_idle_line_detection).await; + + let ch = &mut self.rx_dma; + + match res { + Ok(ReadCompletionEvent::DmaCompleted) => Ok(buffer_len), + Ok(ReadCompletionEvent::Idle) => { + let n = buffer_len - (ch.remaining_transfers() as usize); + Ok(n) + } + Err(e) => Err(e), + } + } } impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> { pub fn new( - _inner: impl Peripheral

+ 'd, + peri: impl Peripheral

+ 'd, rx: impl Peripheral

> + 'd, tx: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, tx_dma: impl Peripheral

+ 'd, rx_dma: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(_inner, rx, tx, tx_dma, rx_dma); + T::enable(); + T::reset(); + + Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config) + } + + pub fn new_with_rtscts( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cts, rts); T::enable(); T::reset(); - let pclk_freq = T::frequency(); - // TODO: better calculation, including error checking and OVER8 if possible. - let div = (pclk_freq.0 + (config.baudrate / 2)) / config.baudrate * T::MULTIPLIER; + unsafe { + rts.set_as_af(rts.af_num(), AFType::OutputPushPull); + cts.set_as_af(cts.af_num(), AFType::Input); + T::regs().cr3().write(|w| { + w.set_rtse(true); + w.set_ctse(true); + }); + } + Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config) + } + + #[cfg(not(usart_v1))] + pub fn new_with_de( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, + de: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(de); + + T::enable(); + T::reset(); + + unsafe { + de.set_as_af(de.af_num(), AFType::OutputPushPull); + T::regs().cr3().write(|w| { + w.set_dem(true); + }); + } + Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + irq: impl Peripheral

+ 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(peri, rx, tx, irq, tx_dma, rx_dma); let r = T::regs(); unsafe { rx.set_as_af(rx.af_num(), AFType::Input); tx.set_as_af(tx.af_num(), AFType::OutputPushPull); - - r.cr2().write(|_w| {}); - r.cr3().write(|_w| {}); - r.brr().write_value(regs::Brr(div)); - r.cr1().write(|w| { - w.set_ue(true); - w.set_te(true); - w.set_re(true); - w.set_m0(if config.parity != Parity::ParityNone { - vals::M0::BIT9 - } else { - vals::M0::BIT8 - }); - w.set_pce(config.parity != Parity::ParityNone); - w.set_ps(match config.parity { - Parity::ParityOdd => vals::Ps::ODD, - Parity::ParityEven => vals::Ps::EVEN, - _ => vals::Ps::EVEN, - }); - }); } + configure(r, &config, T::frequency(), T::MULTIPLIER, true, true); + + irq.set_handler(UartRx::::on_interrupt); + irq.unpend(); + irq.enable(); + + // create state once! + let _s = T::state(); + Self { - tx: UartTx::new(tx_dma), - rx: UartRx::new(rx_dma), - phantom: PhantomData {}, + tx: UartTx { + tx_dma, + phantom: PhantomData, + }, + rx: UartRx { + _peri: peri, + rx_dma, + detect_previous_overrun: config.detect_previous_overrun, + }, } } @@ -295,6 +745,13 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> { self.rx.blocking_read(buffer) } + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result + where + RxDma: crate::usart::RxDma, + { + self.rx.read_until_idle(buffer).await + } + /// Split the Uart into a transmitter and receiver, which is /// particuarly useful when having two tasks correlating to /// transmitting and receiving. @@ -303,6 +760,48 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> { } } +fn configure(r: Regs, config: &Config, pclk_freq: Hertz, multiplier: u32, enable_rx: bool, enable_tx: bool) { + if !enable_rx && !enable_tx { + panic!("USART: At least one of RX or TX should be enabled"); + } + + // TODO: better calculation, including error checking and OVER8 if possible. + let div = (pclk_freq.0 + (config.baudrate / 2)) / config.baudrate * multiplier; + + unsafe { + r.brr().write_value(regs::Brr(div)); + r.cr2().write(|w| { + w.set_stop(match config.stop_bits { + StopBits::STOP0P5 => vals::Stop::STOP0P5, + StopBits::STOP1 => vals::Stop::STOP1, + StopBits::STOP1P5 => vals::Stop::STOP1P5, + StopBits::STOP2 => vals::Stop::STOP2, + }); + }); + r.cr1().write(|w| { + // enable uart + w.set_ue(true); + // enable transceiver + w.set_te(enable_tx); + // enable receiver + w.set_re(enable_rx); + // configure word size + w.set_m0(if config.parity != Parity::ParityNone { + vals::M0::BIT9 + } else { + vals::M0::BIT8 + }); + // configure parity + w.set_pce(config.parity != Parity::ParityNone); + w.set_ps(match config.parity { + Parity::ParityOdd => vals::Ps::ODD, + Parity::ParityEven => vals::Ps::EVEN, + _ => vals::Ps::EVEN, + }); + }); + } +} + mod eh02 { use super::*; @@ -352,6 +851,7 @@ mod eh1 { Self::Noise => embedded_hal_1::serial::ErrorKind::Noise, Self::Overrun => embedded_hal_1::serial::ErrorKind::Overrun, Self::Parity => embedded_hal_1::serial::ErrorKind::Parity, + Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, } } } @@ -421,6 +921,58 @@ mod eh1 { } } +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eio { + use embedded_io::asynch::Write; + use embedded_io::Io; + + use super::*; + + impl Io for Uart<'_, T, TxDma, RxDma> + where + T: BasicInstance, + { + type Error = Error; + } + + impl Write for Uart<'_, T, TxDma, RxDma> + where + T: BasicInstance, + TxDma: super::TxDma, + { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl Io for UartTx<'_, T, TxDma> + where + T: BasicInstance, + { + type Error = Error; + } + + impl Write for UartTx<'_, T, TxDma> + where + T: BasicInstance, + TxDma: super::TxDma, + { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } +} + #[cfg(all( feature = "unstable-traits", feature = "nightly", @@ -536,13 +1088,30 @@ unsafe fn clear_interrupt_flags(r: Regs, sr: regs::Isr) { } pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + use super::*; + pub struct State { + pub rx_waker: AtomicWaker, + pub tx_waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + } + } + } + pub trait BasicInstance: crate::rcc::RccPeripheral { const MULTIPLIER: u32; type Interrupt: crate::interrupt::Interrupt; fn regs() -> Regs; + fn state() -> &'static State; } pub trait FullInstance: BasicInstance { @@ -550,7 +1119,7 @@ pub(crate) mod sealed { } } -pub trait BasicInstance: sealed::BasicInstance {} +pub trait BasicInstance: Peripheral

+ sealed::BasicInstance + 'static + Send {} pub trait FullInstance: sealed::FullInstance {} @@ -559,6 +1128,7 @@ pin_trait!(TxPin, BasicInstance); pin_trait!(CtsPin, BasicInstance); pin_trait!(RtsPin, BasicInstance); pin_trait!(CkPin, BasicInstance); +pin_trait!(DePin, BasicInstance); dma_trait!(TxDma, BasicInstance); dma_trait!(RxDma, BasicInstance); @@ -572,6 +1142,11 @@ macro_rules! impl_lpuart { fn regs() -> Regs { Regs(crate::pac::$inst.0) } + + fn state() -> &'static crate::usart::sealed::State { + static STATE: crate::usart::sealed::State = crate::usart::sealed::State::new(); + &STATE + } } impl BasicInstance for peripherals::$inst {} @@ -580,7 +1155,7 @@ macro_rules! impl_lpuart { foreach_interrupt!( ($inst:ident, lpuart, $block:ident, $signal_name:ident, $irq:ident) => { - impl_lpuart!($inst, $irq, 255); + impl_lpuart!($inst, $irq, 256); }; ($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => { diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index 2654f156a..0355c5f14 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -1,11 +1,10 @@ #![macro_use] -use core::future::{poll_fn, Future}; +use core::future::poll_fn; use core::marker::PhantomData; -use core::sync::atomic::Ordering; +use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; -use atomic_polyfill::{AtomicBool, AtomicU8}; use embassy_hal_common::into_ref; use embassy_sync::waitqueue::AtomicWaker; use embassy_time::{block_for, Duration}; @@ -35,10 +34,9 @@ static BUS_WAKER: AtomicWaker = NEW_AW; static EP0_SETUP: AtomicBool = AtomicBool::new(false); static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; -static IRQ_FLAGS: AtomicU8 = AtomicU8::new(0); -const IRQ_FLAG_RESET: u8 = 0x01; -const IRQ_FLAG_SUSPEND: u8 = 0x02; -const IRQ_FLAG_RESUME: u8 = 0x04; +static IRQ_RESET: AtomicBool = AtomicBool::new(false); +static IRQ_SUSPEND: AtomicBool = AtomicBool::new(false); +static IRQ_RESUME: AtomicBool = AtomicBool::new(false); fn convert_type(t: EndpointType) -> EpType { match t { @@ -156,6 +154,7 @@ impl<'d, T: Instance> Driver<'d, T> { block_for(Duration::from_millis(100)); + #[cfg(not(usb_v4))] regs.btable().write(|w| w.set_btable(0)); dp.set_as_af(dp.af_num(), AFType::OutputPushPull); @@ -184,47 +183,51 @@ impl<'d, T: Instance> Driver<'d, T> { let istr = regs.istr().read(); - let mut flags: u8 = 0; - if istr.susp() { //trace!("USB IRQ: susp"); - flags |= IRQ_FLAG_SUSPEND; + IRQ_SUSPEND.store(true, Ordering::Relaxed); regs.cntr().modify(|w| { w.set_fsusp(true); w.set_lpmode(true); - }) + }); + + // Write 0 to clear. + let mut clear = regs::Istr(!0); + clear.set_susp(false); + regs.istr().write_value(clear); + + // Wake main thread. + BUS_WAKER.wake(); } if istr.wkup() { //trace!("USB IRQ: wkup"); - flags |= IRQ_FLAG_RESUME; + IRQ_RESUME.store(true, Ordering::Relaxed); regs.cntr().modify(|w| { w.set_fsusp(false); w.set_lpmode(false); - }) + }); + + // Write 0 to clear. + let mut clear = regs::Istr(!0); + clear.set_wkup(false); + regs.istr().write_value(clear); + + // Wake main thread. + BUS_WAKER.wake(); } if istr.reset() { //trace!("USB IRQ: reset"); - flags |= IRQ_FLAG_RESET; + IRQ_RESET.store(true, Ordering::Relaxed); // Write 0 to clear. let mut clear = regs::Istr(!0); clear.set_reset(false); regs.istr().write_value(clear); - } - if flags != 0 { - // Send irqs to main thread. - IRQ_FLAGS.fetch_or(flags, Ordering::AcqRel); + // Wake main thread. BUS_WAKER.wake(); - - // Clear them - let mut mask = regs::Istr(0); - mask.set_wkup(true); - mask.set_susp(true); - mask.set_reset(true); - regs.istr().write_value(regs::Istr(!(istr.0 & mask.0))); } if istr.ctr() { @@ -265,13 +268,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, driver::EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -342,7 +345,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, buf, }) @@ -359,18 +362,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { @@ -429,26 +432,22 @@ pub struct Bus<'d, T: Instance> { } impl<'d, T: Instance> driver::Bus for Bus<'d, T> { - type PollFuture<'a> = impl Future + 'a where Self: 'a; - - fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { + async fn poll(&mut self) -> Event { poll_fn(move |cx| unsafe { BUS_WAKER.register(cx.waker()); if self.inited { let regs = T::regs(); - let flags = IRQ_FLAGS.load(Ordering::Acquire); - - if flags & IRQ_FLAG_RESUME != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel); + if IRQ_RESUME.load(Ordering::Acquire) { + IRQ_RESUME.store(false, Ordering::Relaxed); return Poll::Ready(Event::Resume); } - if flags & IRQ_FLAG_RESET != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel); + if IRQ_RESET.load(Ordering::Acquire) { + IRQ_RESET.store(false, Ordering::Relaxed); - trace!("RESET REGS WRITINGINGING"); + trace!("RESET"); regs.daddr().write(|w| { w.set_ef(true); w.set_add(0); @@ -477,8 +476,8 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { return Poll::Ready(Event::Reset); } - if flags & IRQ_FLAG_SUSPEND != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel); + if IRQ_SUSPEND.load(Ordering::Acquire) { + IRQ_SUSPEND.store(false, Ordering::Relaxed); return Poll::Ready(Event::Suspend); } @@ -488,18 +487,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { return Poll::Ready(Event::PowerDetected); } }) - } - - #[inline] - fn set_address(&mut self, addr: u8) { - let regs = T::regs(); - trace!("setting addr: {}", addr); - unsafe { - regs.daddr().write(|w| { - w.set_ef(true); - w.set_add(addr); - }) - } + .await } fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { @@ -598,22 +586,11 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { trace!("EPR after: {:04x}", unsafe { reg.read() }.0); } - type EnableFuture<'a> = impl Future + 'a where Self: 'a; + async fn enable(&mut self) {} + async fn disable(&mut self) {} - fn enable(&mut self) -> Self::EnableFuture<'_> { - async move {} - } - - type DisableFuture<'a> = impl Future + 'a where Self: 'a; - - fn disable(&mut self) -> Self::DisableFuture<'_> { - async move {} - } - - type RemoteWakeupFuture<'a> = impl Future> + 'a where Self: 'a; - - fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> { - async move { Err(Unsupported) } + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) } } @@ -676,24 +653,20 @@ impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { - async move { - trace!("wait_enabled OUT WAITING"); - let index = self.info.addr.index(); - poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(index).read() }.stat_tx() == Stat::DISABLED { - Poll::Pending - } else { - Poll::Ready(()) - } - }) - .await; - trace!("wait_enabled OUT OK"); - } + async fn wait_enabled(&mut self) { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let regs = T::regs(); + if unsafe { regs.epr(index).read() }.stat_tx() == Stat::DISABLED { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("wait_enabled OUT OK"); } } @@ -702,116 +675,104 @@ impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { - async move { - trace!("wait_enabled OUT WAITING"); - let index = self.info.addr.index(); - poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(index).read() }.stat_rx() == Stat::DISABLED { - Poll::Pending - } else { - Poll::Ready(()) - } - }) - .await; - trace!("wait_enabled OUT OK"); - } + async fn wait_enabled(&mut self) { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let regs = T::regs(); + if unsafe { regs.epr(index).read() }.stat_rx() == Stat::DISABLED { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("wait_enabled OUT OK"); } } impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - trace!("READ WAITING, buf.len() = {}", buf.len()); - let index = self.info.addr.index(); - let stat = poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - let stat = unsafe { regs.epr(index).read() }.stat_rx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) - } else { - Poll::Pending - } - }) - .await; - - if stat == Stat::DISABLED { - return Err(EndpointError::Disabled); - } - - let rx_len = self.read_data(buf)?; - + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("READ WAITING, buf.len() = {}", buf.len()); + let index = self.info.addr.index(); + let stat = poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); let regs = T::regs(); - unsafe { - 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(Stat::NAK.0 ^ Stat::VALID.0)); - w.set_stat_tx(Stat(0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - }; - trace!("READ OK, rx_len = {}", rx_len); + let stat = unsafe { regs.epr(index).read() }.stat_rx(); + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } + }) + .await; - Ok(rx_len) + if stat == Stat::DISABLED { + return Err(EndpointError::Disabled); } + + let rx_len = self.read_data(buf)?; + + let regs = T::regs(); + unsafe { + 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(Stat::NAK.0 ^ Stat::VALID.0)); + w.set_stat_tx(Stat(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }) + }; + trace!("READ OK, rx_len = {}", rx_len); + + Ok(rx_len) } } impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - if buf.len() > self.info.max_packet_size as usize { - return Err(EndpointError::BufferOverflow); - } - - let index = self.info.addr.index(); - - trace!("WRITE WAITING"); - let stat = poll_fn(|cx| { - EP_IN_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - let stat = unsafe { regs.epr(index).read() }.stat_tx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) - } else { - Poll::Pending - } - }) - .await; - - if stat == Stat::DISABLED { - return Err(EndpointError::Disabled); - } - - self.write_data(buf); - - let regs = T::regs(); - unsafe { - 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(Stat::NAK.0 ^ Stat::VALID.0)); - w.set_stat_rx(Stat(0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - }; - - trace!("WRITE OK"); - - Ok(()) + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); } + + let index = self.info.addr.index(); + + trace!("WRITE WAITING"); + let stat = poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + let regs = T::regs(); + let stat = unsafe { regs.epr(index).read() }.stat_tx(); + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } + }) + .await; + + if stat == Stat::DISABLED { + return Err(EndpointError::Disabled); + } + + self.write_data(buf); + + let regs = T::regs(); + unsafe { + 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(Stat::NAK.0 ^ Stat::VALID.0)); + w.set_stat_rx(Stat(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }) + }; + + trace!("WRITE OK"); + + Ok(()) } } @@ -823,84 +784,16 @@ pub struct ControlPipe<'d, T: Instance> { } impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { - type SetupFuture<'a> = impl Future + 'a where Self: 'a; - type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; - type DataInFuture<'a> = impl Future> + 'a where Self: 'a; - type AcceptFuture<'a> = impl Future + 'a where Self: 'a; - type RejectFuture<'a> = impl Future + 'a where Self: 'a; - fn max_packet_size(&self) -> usize { usize::from(self.max_packet_size) } - fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { - async move { - loop { - trace!("SETUP read waiting"); - poll_fn(|cx| { - EP_OUT_WAKERS[0].register(cx.waker()); - if EP0_SETUP.load(Ordering::Relaxed) { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - let mut buf = [0; 8]; - let rx_len = self.ep_out.read_data(&mut buf); - if rx_len != Ok(8) { - trace!("SETUP read failed: {:?}", rx_len); - continue; - } - - EP0_SETUP.store(false, Ordering::Relaxed); - - trace!("SETUP read ok"); - return buf; - } - } - } - - fn data_out<'a>(&'a mut self, buf: &'a mut [u8], first: bool, last: bool) -> Self::DataOutFuture<'a> { - async move { - let regs = T::regs(); - - // When a SETUP is received, Stat/Stat is set to NAK. - // On first transfer, we must set Stat=VALID, to get the OUT data stage. - // We want Stat=STALL so that the host gets a STALL if it switches to the status - // stage too soon, except in the last transfer we set Stat=NAK so that it waits - // for the status stage, which we will ACK or STALL later. - if first || last { - let mut stat_rx = 0; - let mut stat_tx = 0; - if first { - // change NAK -> VALID - stat_rx ^= Stat::NAK.0 ^ Stat::VALID.0; - stat_tx ^= Stat::NAK.0 ^ Stat::STALL.0; - } - if last { - // change STALL -> VALID - stat_tx ^= Stat::STALL.0 ^ Stat::NAK.0; - } - // Note: if this is the first AND last transfer, the above effectively - // changes stat_tx like NAK -> NAK, so noop. - unsafe { - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(stat_rx)); - w.set_stat_tx(Stat(stat_tx)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - } - } - - trace!("data_out WAITING, buf.len() = {}", buf.len()); + async fn setup(&mut self) -> [u8; 8] { + loop { + trace!("SETUP read waiting"); poll_fn(|cx| { EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(0).read() }.stat_rx() == Stat::NAK { + if EP0_SETUP.load(Ordering::Relaxed) { Poll::Ready(()) } else { Poll::Pending @@ -908,157 +801,222 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { }) .await; - if EP0_SETUP.load(Ordering::Relaxed) { - trace!("received another SETUP, aborting data_out."); - return Err(EndpointError::Disabled); + let mut buf = [0; 8]; + let rx_len = self.ep_out.read_data(&mut buf); + if rx_len != Ok(8) { + trace!("SETUP read failed: {:?}", rx_len); + continue; } - let rx_len = self.ep_out.read_data(buf)?; + EP0_SETUP.store(false, Ordering::Relaxed); + trace!("SETUP read ok"); + return buf; + } + } + + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result { + let regs = T::regs(); + + // When a SETUP is received, Stat/Stat is set to NAK. + // On first transfer, we must set Stat=VALID, to get the OUT data stage. + // We want Stat=STALL so that the host gets a STALL if it switches to the status + // stage too soon, except in the last transfer we set Stat=NAK so that it waits + // for the status stage, which we will ACK or STALL later. + if first || last { + let mut stat_rx = 0; + let mut stat_tx = 0; + if first { + // change NAK -> VALID + stat_rx ^= Stat::NAK.0 ^ Stat::VALID.0; + stat_tx ^= Stat::NAK.0 ^ Stat::STALL.0; + } + if last { + // change STALL -> VALID + stat_tx ^= Stat::STALL.0 ^ Stat::NAK.0; + } + // Note: if this is the first AND last transfer, the above effectively + // changes stat_tx like NAK -> NAK, so noop. unsafe { regs.epr(0).write(|w| { w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(match last { - // If last, set STAT_RX=STALL. - true => Stat::NAK.0 ^ Stat::STALL.0, - // Otherwise, set STAT_RX=VALID, to allow the host to send the next packet. - false => Stat::NAK.0 ^ Stat::VALID.0, - })); + w.set_stat_rx(Stat(stat_rx)); + w.set_stat_tx(Stat(stat_tx)); w.set_ctr_rx(true); // don't clear w.set_ctr_tx(true); // don't clear }) - }; - - Ok(rx_len) + } } + + trace!("data_out WAITING, buf.len() = {}", buf.len()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if unsafe { regs.epr(0).read() }.stat_rx() == Stat::NAK { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + if EP0_SETUP.load(Ordering::Relaxed) { + trace!("received another SETUP, aborting data_out."); + return Err(EndpointError::Disabled); + } + + let rx_len = self.ep_out.read_data(buf)?; + + unsafe { + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat(match last { + // If last, set STAT_RX=STALL. + true => Stat::NAK.0 ^ Stat::STALL.0, + // Otherwise, set STAT_RX=VALID, to allow the host to send the next packet. + false => Stat::NAK.0 ^ Stat::VALID.0, + })); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }) + }; + + Ok(rx_len) } - fn data_in<'a>(&'a mut self, buf: &'a [u8], first: bool, last: bool) -> Self::DataInFuture<'a> { - async move { - trace!("control: data_in"); + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in"); - if buf.len() > self.ep_in.info.max_packet_size as usize { - return Err(EndpointError::BufferOverflow); + if data.len() > self.ep_in.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + let regs = T::regs(); + + // When a SETUP is received, Stat is set to NAK. + // We want it to be STALL in non-last transfers. + // We want it to be VALID in last transfer, so the HW does the status stage. + if first || last { + let mut stat_rx = 0; + if first { + // change NAK -> STALL + stat_rx ^= Stat::NAK.0 ^ Stat::STALL.0; } - - let regs = T::regs(); - - // When a SETUP is received, Stat is set to NAK. - // We want it to be STALL in non-last transfers. - // We want it to be VALID in last transfer, so the HW does the status stage. - if first || last { - let mut stat_rx = 0; - if first { - // change NAK -> STALL - stat_rx ^= Stat::NAK.0 ^ Stat::STALL.0; - } - if last { - // change STALL -> VALID - stat_rx ^= Stat::STALL.0 ^ Stat::VALID.0; - } - // Note: if this is the first AND last transfer, the above effectively - // does a change of NAK -> VALID. - unsafe { - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(stat_rx)); - w.set_ep_kind(last); // set OUT_STATUS if last. - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - } + if last { + // change STALL -> VALID + stat_rx ^= Stat::STALL.0 ^ Stat::VALID.0; } - - trace!("WRITE WAITING"); - poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); - EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - if EP0_SETUP.load(Ordering::Relaxed) { - trace!("received another SETUP, aborting data_in."); - return Err(EndpointError::Disabled); - } - - self.ep_in.write_data(buf); - - let regs = T::regs(); + // Note: if this is the first AND last transfer, the above effectively + // does a change of NAK -> VALID. unsafe { regs.epr(0).write(|w| { w.set_ep_type(EpType::CONTROL); - w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0)); + w.set_stat_rx(Stat(stat_rx)); w.set_ep_kind(last); // set OUT_STATUS if last. w.set_ctr_rx(true); // don't clear w.set_ctr_tx(true); // don't clear }) - }; - - trace!("WRITE OK"); - - Ok(()) - } - } - - fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a> { - async move { - let regs = T::regs(); - trace!("control: accept"); - - self.ep_in.write_data(&[]); - - // Set OUT=stall, IN=accept - unsafe { - let epr = regs.epr(0).read(); - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0)); - w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::VALID.0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }); } - trace!("control: accept WAITING"); + } - // Wait is needed, so that we don't set the address too soon, breaking the status stage. - // (embassy-usb sets the address after accept() returns) - poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK { - Poll::Ready(()) - } else { - Poll::Pending - } + trace!("WRITE WAITING"); + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + if EP0_SETUP.load(Ordering::Relaxed) { + trace!("received another SETUP, aborting data_in."); + return Err(EndpointError::Disabled); + } + + self.ep_in.write_data(data); + + let regs = T::regs(); + unsafe { + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0)); + w.set_ep_kind(last); // set OUT_STATUS if last. + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear }) - .await; + }; - trace!("control: accept OK"); + trace!("WRITE OK"); + + Ok(()) + } + + async fn accept(&mut self) { + let regs = T::regs(); + trace!("control: accept"); + + self.ep_in.write_data(&[]); + + // Set OUT=stall, IN=accept + unsafe { + let epr = regs.epr(0).read(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0)); + w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::VALID.0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + } + trace!("control: accept WAITING"); + + // Wait is needed, so that we don't set the address too soon, breaking the status stage. + // (embassy-usb sets the address after accept() returns) + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + trace!("control: accept OK"); + } + + async fn reject(&mut self) { + let regs = T::regs(); + trace!("control: reject"); + + // Set IN+OUT to stall + unsafe { + let epr = regs.epr(0).read(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0)); + w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::STALL.0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); } } - fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a> { - async move { - let regs = T::regs(); - trace!("control: reject"); + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; - // Set IN+OUT to stall - unsafe { - let epr = regs.epr(0).read(); - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0)); - w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::STALL.0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }); - } + let regs = T::regs(); + trace!("setting addr: {}", addr); + unsafe { + regs.daddr().write(|w| { + w.set_ef(true); + w.set_add(addr); + }) } } } diff --git a/embassy-stm32/src/usb_otg.rs b/embassy-stm32/src/usb_otg.rs deleted file mode 100644 index f7faf12a8..000000000 --- a/embassy-stm32/src/usb_otg.rs +++ /dev/null @@ -1,213 +0,0 @@ -use core::marker::PhantomData; - -use embassy_hal_common::into_ref; - -use crate::gpio::sealed::AFType; -use crate::rcc::RccPeripheral; -use crate::{peripherals, Peripheral}; - -macro_rules! config_ulpi_pins { - ($($pin:ident),*) => { - into_ref!($($pin),*); - // NOTE(unsafe) Exclusive access to the registers - critical_section::with(|_| unsafe { - $( - $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); - #[cfg(gpio_v2)] - $pin.set_speed(crate::gpio::Speed::VeryHigh); - )* - }) - }; -} - -/// USB PHY type -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PhyType { - /// Internal Full-Speed PHY - /// - /// Available on most High-Speed peripherals. - InternalFullSpeed, - /// Internal High-Speed PHY - /// - /// Available on a few STM32 chips. - InternalHighSpeed, - /// External ULPI High-Speed PHY - ExternalHighSpeed, -} - -pub struct UsbOtg<'d, T: Instance> { - phantom: PhantomData<&'d mut T>, - _phy_type: PhyType, -} - -impl<'d, T: Instance> UsbOtg<'d, T> { - /// Initializes USB OTG peripheral with internal Full-Speed PHY - pub fn new_fs( - _peri: impl Peripheral

+ 'd, - dp: impl Peripheral

> + 'd, - dm: impl Peripheral

> + 'd, - ) -> Self { - into_ref!(dp, dm); - - unsafe { - dp.set_as_af(dp.af_num(), AFType::OutputPushPull); - dm.set_as_af(dm.af_num(), AFType::OutputPushPull); - } - - Self { - phantom: PhantomData, - _phy_type: PhyType::InternalFullSpeed, - } - } - - /// Initializes USB OTG peripheral with external High-Speed PHY - pub fn new_hs_ulpi( - _peri: impl Peripheral

+ '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, - ) -> 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 - ); - - Self { - phantom: PhantomData, - _phy_type: PhyType::ExternalHighSpeed, - } - } -} - -impl<'d, T: Instance> Drop for UsbOtg<'d, T> { - fn drop(&mut self) { - T::reset(); - T::disable(); - } -} - -pub(crate) mod sealed { - pub trait Instance { - const REGISTERS: *const (); - const HIGH_SPEED: bool; - const FIFO_DEPTH_WORDS: usize; - const ENDPOINT_COUNT: usize; - } -} - -pub trait Instance: sealed::Instance + RccPeripheral {} - -// Internal PHY pins -pin_trait!(DpPin, Instance); -pin_trait!(DmPin, Instance); - -// External PHY pins -pin_trait!(UlpiClkPin, Instance); -pin_trait!(UlpiDirPin, Instance); -pin_trait!(UlpiNxtPin, Instance); -pin_trait!(UlpiStpPin, Instance); -pin_trait!(UlpiD0Pin, Instance); -pin_trait!(UlpiD1Pin, Instance); -pin_trait!(UlpiD2Pin, Instance); -pin_trait!(UlpiD3Pin, Instance); -pin_trait!(UlpiD4Pin, Instance); -pin_trait!(UlpiD5Pin, Instance); -pin_trait!(UlpiD6Pin, Instance); -pin_trait!(UlpiD7Pin, Instance); - -foreach_peripheral!( - (otgfs, $inst:ident) => { - impl sealed::Instance for peripherals::$inst { - const REGISTERS: *const () = crate::pac::$inst.0 as *const (); - const HIGH_SPEED: bool = false; - - cfg_if::cfg_if! { - if #[cfg(stm32f1)] { - const FIFO_DEPTH_WORDS: usize = 128; - const ENDPOINT_COUNT: usize = 8; - } else if #[cfg(any( - stm32f2, - stm32f401, - stm32f405, - stm32f407, - stm32f411, - stm32f415, - stm32f417, - stm32f427, - stm32f429, - stm32f437, - stm32f439, - ))] { - const FIFO_DEPTH_WORDS: usize = 320; - const ENDPOINT_COUNT: usize = 4; - } else if #[cfg(any( - stm32f412, - stm32f413, - stm32f423, - stm32f446, - stm32f469, - stm32f479, - stm32f7, - stm32l4, - stm32u5, - ))] { - const FIFO_DEPTH_WORDS: usize = 320; - const ENDPOINT_COUNT: usize = 6; - } else if #[cfg(stm32g0x1)] { - const FIFO_DEPTH_WORDS: usize = 512; - const ENDPOINT_COUNT: usize = 8; - } else { - compile_error!("USB_OTG_FS peripheral is not supported by this chip."); - } - } - } - - impl Instance for peripherals::$inst {} - }; - - (otghs, $inst:ident) => { - impl sealed::Instance for peripherals::$inst { - const REGISTERS: *const () = crate::pac::$inst.0 as *const (); - const HIGH_SPEED: bool = true; - - cfg_if::cfg_if! { - if #[cfg(any( - stm32f2, - stm32f405, - stm32f407, - stm32f415, - stm32f417, - stm32f427, - stm32f429, - stm32f437, - stm32f439, - ))] { - const FIFO_DEPTH_WORDS: usize = 1024; - const ENDPOINT_COUNT: usize = 6; - } else if #[cfg(any( - stm32f446, - stm32f469, - stm32f479, - stm32f7, - stm32h7, - ))] { - const FIFO_DEPTH_WORDS: usize = 1024; - const ENDPOINT_COUNT: usize = 9; - } else { - compile_error!("USB_OTG_HS peripheral is not supported by this chip."); - } - } - } - - impl Instance for peripherals::$inst {} - }; -); diff --git a/embassy-stm32/src/usb_otg/mod.rs b/embassy-stm32/src/usb_otg/mod.rs new file mode 100644 index 000000000..84fef78cb --- /dev/null +++ b/embassy-stm32/src/usb_otg/mod.rs @@ -0,0 +1,161 @@ +use embassy_cortex_m::interrupt::Interrupt; + +use crate::peripherals; +use crate::rcc::RccPeripheral; + +#[cfg(feature = "nightly")] +mod usb; +#[cfg(feature = "nightly")] +pub use usb::*; + +// Using Instance::ENDPOINT_COUNT requires feature(const_generic_expr) so just define maximum eps +#[cfg(feature = "nightly")] +const MAX_EP_COUNT: usize = 9; + +pub(crate) mod sealed { + pub trait Instance { + const HIGH_SPEED: bool; + const FIFO_DEPTH_WORDS: u16; + const ENDPOINT_COUNT: usize; + + fn regs() -> crate::pac::otg::Otg; + #[cfg(feature = "nightly")] + fn state() -> &'static super::State<{ super::MAX_EP_COUNT }>; + } +} + +pub trait Instance: sealed::Instance + RccPeripheral { + type Interrupt: Interrupt; +} + +// Internal PHY pins +pin_trait!(DpPin, Instance); +pin_trait!(DmPin, Instance); + +// External PHY pins +pin_trait!(UlpiClkPin, Instance); +pin_trait!(UlpiDirPin, Instance); +pin_trait!(UlpiNxtPin, Instance); +pin_trait!(UlpiStpPin, Instance); +pin_trait!(UlpiD0Pin, Instance); +pin_trait!(UlpiD1Pin, Instance); +pin_trait!(UlpiD2Pin, Instance); +pin_trait!(UlpiD3Pin, Instance); +pin_trait!(UlpiD4Pin, Instance); +pin_trait!(UlpiD5Pin, Instance); +pin_trait!(UlpiD6Pin, Instance); +pin_trait!(UlpiD7Pin, Instance); + +foreach_interrupt!( + (USB_OTG_FS, otg, $block:ident, GLOBAL, $irq:ident) => { + impl sealed::Instance for peripherals::USB_OTG_FS { + const HIGH_SPEED: bool = false; + + cfg_if::cfg_if! { + if #[cfg(stm32f1)] { + const FIFO_DEPTH_WORDS: u16 = 128; + const ENDPOINT_COUNT: usize = 8; + } else if #[cfg(any( + stm32f2, + stm32f401, + stm32f405, + stm32f407, + stm32f411, + stm32f415, + stm32f417, + stm32f427, + stm32f429, + stm32f437, + stm32f439, + ))] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 4; + } else if #[cfg(any( + stm32f412, + stm32f413, + stm32f423, + stm32f446, + stm32f469, + stm32f479, + stm32f7, + stm32l4, + stm32u5, + ))] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 6; + } else if #[cfg(stm32g0x1)] { + const FIFO_DEPTH_WORDS: u16 = 512; + const ENDPOINT_COUNT: usize = 8; + } else if #[cfg(stm32h7)] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else { + compile_error!("USB_OTG_FS peripheral is not supported by this chip."); + } + } + + fn regs() -> crate::pac::otg::Otg { + crate::pac::USB_OTG_FS + } + + #[cfg(feature = "nightly")] + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::USB_OTG_FS { + type Interrupt = crate::interrupt::$irq; + } + }; + + (USB_OTG_HS, otg, $block:ident, GLOBAL, $irq:ident) => { + impl sealed::Instance for peripherals::USB_OTG_HS { + const HIGH_SPEED: bool = true; + + cfg_if::cfg_if! { + if #[cfg(any( + stm32f2, + stm32f405, + stm32f407, + stm32f415, + stm32f417, + stm32f427, + stm32f429, + stm32f437, + stm32f439, + ))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 6; + } else if #[cfg(any( + stm32f446, + stm32f469, + stm32f479, + stm32f7, + stm32h7, + ))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else { + compile_error!("USB_OTG_HS peripheral is not supported by this chip."); + } + } + + fn regs() -> crate::pac::otg::Otg { + // OTG HS registers are a superset of FS registers + crate::pac::otg::Otg(crate::pac::USB_OTG_HS.0) + } + + #[cfg(feature = "nightly")] + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::USB_OTG_HS { + type Interrupt = crate::interrupt::$irq; + } + }; +); diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs new file mode 100644 index 000000000..96c574ca8 --- /dev/null +++ b/embassy-stm32/src/usb_otg/usb.rs @@ -0,0 +1,1385 @@ +use core::cell::UnsafeCell; +use core::marker::PhantomData; +use core::task::Poll; + +use atomic_polyfill::{AtomicBool, AtomicU16, Ordering}; +use embassy_cortex_m::interrupt::InterruptExt; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver::{ + self, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, EndpointOut, + EndpointType, Event, Unsupported, +}; +use futures::future::poll_fn; + +use super::*; +use crate::gpio::sealed::AFType; +use crate::pac::otg::{regs, vals}; +use crate::rcc::sealed::RccPeripheral; +use crate::time::Hertz; + +macro_rules! config_ulpi_pins { + ($($pin:ident),*) => { + into_ref!($($pin),*); + // NOTE(unsafe) Exclusive access to the registers + critical_section::with(|_| unsafe { + $( + $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); + #[cfg(gpio_v2)] + $pin.set_speed(crate::gpio::Speed::VeryHigh); + )* + }) + }; +} + +// From `synopsys-usb-otg` crate: +// This calculation doesn't correspond to one in a Reference Manual. +// In fact, the required number of words is higher than indicated in RM. +// The following numbers are pessimistic and were figured out empirically. +const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30; + +/// USB PHY type +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PhyType { + /// Internal Full-Speed PHY + /// + /// Available on most High-Speed peripherals. + InternalFullSpeed, + /// Internal High-Speed PHY + /// + /// Available on a few STM32 chips. + InternalHighSpeed, + /// External ULPI High-Speed PHY + ExternalHighSpeed, +} + +impl PhyType { + pub fn internal(&self) -> bool { + match self { + PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, + PhyType::ExternalHighSpeed => false, + } + } + + pub fn high_speed(&self) -> bool { + match self { + PhyType::InternalFullSpeed => false, + PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, + } + } + + pub fn to_dspd(&self) -> vals::Dspd { + match self { + PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, + PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, + PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, + } + } +} + +/// Indicates that [State::ep_out_buffers] is empty. +const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX; + +pub struct State { + /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true. + ep0_setup_data: UnsafeCell<[u8; 8]>, + ep0_setup_ready: AtomicBool, + ep_in_wakers: [AtomicWaker; EP_COUNT], + ep_out_wakers: [AtomicWaker; EP_COUNT], + /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint. + /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY]. + ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT], + ep_out_size: [AtomicU16; EP_COUNT], + bus_waker: AtomicWaker, +} + +unsafe impl Send for State {} +unsafe impl Sync for State {} + +impl State { + pub const fn new() -> Self { + const NEW_AW: AtomicWaker = AtomicWaker::new(); + const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _); + const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY); + + Self { + ep0_setup_data: UnsafeCell::new([0u8; 8]), + ep0_setup_ready: AtomicBool::new(false), + ep_in_wakers: [NEW_AW; EP_COUNT], + ep_out_wakers: [NEW_AW; EP_COUNT], + ep_out_buffers: [NEW_BUF; EP_COUNT], + ep_out_size: [NEW_SIZE; EP_COUNT], + bus_waker: NEW_AW, + } + } +} + +#[derive(Debug, Clone, Copy)] +struct EndpointData { + ep_type: EndpointType, + max_packet_size: u16, + fifo_size_words: u16, +} + +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + irq: PeripheralRef<'d, T::Interrupt>, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + ep_out_buffer: &'d mut [u8], + ep_out_buffer_offset: usize, + phy_type: PhyType, +} + +impl<'d, T: Instance> Driver<'d, T> { + /// Initializes USB OTG peripheral with internal Full-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied 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( + _peri: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + dp: impl Peripheral

> + 'd, + dm: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + ) -> Self { + into_ref!(dp, dm, irq); + + unsafe { + dp.set_as_af(dp.af_num(), AFType::OutputPushPull); + dm.set_as_af(dm.af_num(), AFType::OutputPushPull); + } + + Self { + phantom: PhantomData, + irq, + ep_in: [None; MAX_EP_COUNT], + ep_out: [None; MAX_EP_COUNT], + ep_out_buffer, + ep_out_buffer_offset: 0, + phy_type: PhyType::InternalFullSpeed, + } + } + + /// Initializes USB OTG peripheral with external High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs_ulpi( + _peri: impl Peripheral

+ 'd, + irq: impl Peripheral

+ '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], + ) -> Self { + assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB"); + + 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 + ); + + into_ref!(irq); + + Self { + phantom: PhantomData, + irq, + ep_in: [None; MAX_EP_COUNT], + ep_out: [None; MAX_EP_COUNT], + ep_out_buffer, + ep_out_buffer_offset: 0, + phy_type: PhyType::ExternalHighSpeed, + } + } + + // Returns total amount of words (u32) allocated in dedicated FIFO + fn allocated_fifo_words(&self) -> u16 { + RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in) + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + if D::dir() == Direction::Out { + if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() { + error!("Not enough endpoint out buffer capacity"); + return Err(EndpointAllocError); + } + }; + + let fifo_size_words = match D::dir() { + Direction::Out => (max_packet_size + 3) / 4, + // INEPTXFD requires minimum size of 16 words + Direction::In => u16::max((max_packet_size + 3) / 4, 16), + }; + + if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS { + error!("Not enough FIFO capacity"); + return Err(EndpointAllocError); + } + + let eps = match D::dir() { + Direction::Out => &mut self.ep_out, + Direction::In => &mut self.ep_in, + }; + + // Find free endpoint slot + let slot = eps.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 && ep_type != EndpointType::Control { + // reserved for control pipe + false + } else { + ep.is_none() + } + }); + + let index = match slot { + Some((index, ep)) => { + *ep = Some(EndpointData { + ep_type, + max_packet_size, + fifo_size_words, + }); + index + } + None => { + error!("No free endpoints available"); + return Err(EndpointAllocError); + } + }; + + trace!(" index={}", index); + + if D::dir() == Direction::Out { + // Buffer capacity check was done above, now allocation cannot fail + unsafe { + *T::state().ep_out_buffers[index].get() = + self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _); + } + self.ep_out_buffer_offset += max_packet_size as usize; + } + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + }) + } +} + +impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let ep_out = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) + .unwrap(); + let ep_in = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) + .unwrap(); + assert_eq!(ep_out.info.addr.index(), 0); + assert_eq!(ep_in.info.addr.index(), 0); + + trace!("start"); + + ( + Bus { + phantom: PhantomData, + irq: self.irq, + ep_in: self.ep_in, + ep_out: self.ep_out, + phy_type: self.phy_type, + enabled: false, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + ep_out, + ep_in, + }, + ) + } +} + +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + irq: PeripheralRef<'d, T::Interrupt>, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + phy_type: PhyType, + enabled: bool, +} + +impl<'d, T: Instance> Bus<'d, T> { + fn restore_irqs() { + // SAFETY: atomic write + unsafe { + T::regs().gintmsk().write(|w| { + w.set_usbrst(true); + w.set_enumdnem(true); + w.set_usbsuspm(true); + w.set_wuim(true); + w.set_iepint(true); + w.set_oepint(true); + w.set_rxflvlm(true); + }); + } + } +} + +impl<'d, T: Instance> Bus<'d, T> { + fn init_fifo(&mut self) { + trace!("init_fifo"); + + let r = T::regs(); + + // Configure RX fifo size. All endpoints share the same FIFO area. + let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out); + trace!("configuring rx fifo size={}", rx_fifo_size_words); + + // SAFETY: register is exclusive to `Bus` with `&mut self` + unsafe { r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)) }; + + // Configure TX (USB in direction) fifo size for each endpoint + let mut fifo_top = rx_fifo_size_words; + for i in 0..T::ENDPOINT_COUNT { + if let Some(ep) = self.ep_in[i] { + trace!( + "configuring tx fifo ep={}, offset={}, size={}", + i, + fifo_top, + ep.fifo_size_words + ); + + let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) }; + + // SAFETY: register is exclusive to `Bus` with `&mut self` + unsafe { + dieptxf.write(|w| { + w.set_fd(ep.fifo_size_words); + w.set_sa(fifo_top); + }); + } + + fifo_top += ep.fifo_size_words; + } + } + + assert!( + fifo_top <= T::FIFO_DEPTH_WORDS, + "FIFO allocations exceeded maximum capacity" + ); + } + + fn configure_endpoints(&mut self) { + trace!("configure_endpoints"); + + let r = T::regs(); + + // Configure IN endpoints + for (index, ep) in self.ep_in.iter().enumerate() { + if let Some(ep) = ep { + // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW + critical_section::with(|_| unsafe { + r.diepctl(index).write(|w| { + if index == 0 { + w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); + } else { + w.set_mpsiz(ep.max_packet_size); + w.set_eptyp(to_eptyp(ep.ep_type)); + w.set_sd0pid_sevnfrm(true); + } + }); + }); + } + } + + // Configure OUT endpoints + for (index, ep) in self.ep_out.iter().enumerate() { + if let Some(ep) = ep { + // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Endpoint` so critical section is needed for RMW + critical_section::with(|_| unsafe { + r.doepctl(index).write(|w| { + if index == 0 { + w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); + } else { + w.set_mpsiz(ep.max_packet_size); + w.set_eptyp(to_eptyp(ep.ep_type)); + w.set_sd0pid_sevnfrm(true); + } + }); + + r.doeptsiz(index).modify(|w| { + w.set_xfrsiz(ep.max_packet_size as _); + if index == 0 { + w.set_rxdpid_stupcnt(1); + } else { + w.set_pktcnt(1); + } + }); + }); + } + } + + // Enable IRQs for allocated endpoints + // SAFETY: register is exclusive to `Bus` with `&mut self` + unsafe { + r.daintmsk().modify(|w| { + w.set_iepm(ep_irq_mask(&self.ep_in)); + // OUT interrupts not used, handled in RXFLVL + // w.set_oepm(ep_irq_mask(&self.ep_out)); + }); + } + } + + fn disable(&mut self) { + self.irq.disable(); + self.irq.remove_handler(); + + ::disable(); + + #[cfg(stm32l4)] + unsafe { + crate::pac::PWR.cr2().modify(|w| w.set_usv(false)); + // Cannot disable PWR, because other peripherals might be using it + } + } + + fn on_interrupt(_: *mut ()) { + trace!("irq"); + let r = T::regs(); + let state = T::state(); + + // SAFETY: atomic read/write + let ints = unsafe { r.gintsts().read() }; + if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() { + // Mask interrupts and notify `Bus` to process them + unsafe { r.gintmsk().write(|_| {}) }; + T::state().bus_waker.wake(); + } + + // Handle RX + // SAFETY: atomic read with no side effects + while unsafe { r.gintsts().read().rxflvl() } { + // SAFETY: atomic "pop" register + let status = unsafe { r.grxstsp().read() }; + let ep_num = status.epnum() as usize; + let len = status.bcnt() as usize; + + assert!(ep_num < T::ENDPOINT_COUNT); + + match status.pktstsd() { + vals::Pktstsd::SETUP_DATA_RX => { + trace!("SETUP_DATA_RX"); + assert!(len == 8, "invalid SETUP packet length={}", len); + assert!(ep_num == 0, "invalid SETUP packet endpoint={}", ep_num); + + if state.ep0_setup_ready.load(Ordering::Relaxed) == false { + // SAFETY: exclusive access ensured by atomic bool + let data = unsafe { &mut *state.ep0_setup_data.get() }; + // SAFETY: FIFO reads are exclusive to this IRQ + unsafe { + data[0..4].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); + data[4..8].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); + } + state.ep0_setup_ready.store(true, Ordering::Release); + state.ep_out_wakers[0].wake(); + } else { + error!("received SETUP before previous finished processing"); + // discard FIFO + // SAFETY: FIFO reads are exclusive to IRQ + unsafe { + r.fifo(0).read(); + r.fifo(0).read(); + } + } + } + vals::Pktstsd::OUT_DATA_RX => { + trace!("OUT_DATA_RX ep={} len={}", ep_num, len); + + if state.ep_out_size[ep_num].load(Ordering::Acquire) == EP_OUT_BUFFER_EMPTY { + // SAFETY: Buffer size is allocated to be equal to endpoint's maximum packet size + // We trust the peripheral to not exceed its configured MPSIZ + let buf = unsafe { core::slice::from_raw_parts_mut(*state.ep_out_buffers[ep_num].get(), len) }; + + for chunk in buf.chunks_mut(4) { + // RX FIFO is shared so always read from fifo(0) + // SAFETY: FIFO reads are exclusive to IRQ + let data = unsafe { r.fifo(0).read().0 }; + chunk.copy_from_slice(&data.to_ne_bytes()[0..chunk.len()]); + } + + state.ep_out_size[ep_num].store(len as u16, Ordering::Release); + state.ep_out_wakers[ep_num].wake(); + } else { + error!("ep_out buffer overflow index={}", ep_num); + + // discard FIFO data + let len_words = (len + 3) / 4; + for _ in 0..len_words { + // SAFETY: FIFO reads are exclusive to IRQ + unsafe { r.fifo(0).read().data() }; + } + } + } + vals::Pktstsd::OUT_DATA_DONE => { + trace!("OUT_DATA_DONE ep={}", ep_num); + } + vals::Pktstsd::SETUP_DATA_DONE => { + trace!("SETUP_DATA_DONE ep={}", ep_num); + } + x => trace!("unknown PKTSTS: {}", x.0), + } + } + + // IN endpoint interrupt + if ints.iepint() { + // SAFETY: atomic read with no side effects + let mut ep_mask = unsafe { r.daint().read().iepint() }; + let mut ep_num = 0; + + // Iterate over endpoints while there are non-zero bits in the mask + while ep_mask != 0 { + if ep_mask & 1 != 0 { + // SAFETY: atomic read with no side effects + let ep_ints = unsafe { r.diepint(ep_num).read() }; + + // clear all + // SAFETY: DIEPINT is exclusive to IRQ + unsafe { r.diepint(ep_num).write_value(ep_ints) }; + + // TXFE is cleared in DIEPEMPMSK + if ep_ints.txfe() { + // SAFETY: DIEPEMPMSK is shared with `Endpoint` so critical section is needed for RMW + critical_section::with(|_| unsafe { + r.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() & !(1 << ep_num)); + }); + }); + } + + state.ep_in_wakers[ep_num].wake(); + trace!("in ep={} irq val={:b}", ep_num, ep_ints.0); + } + + ep_mask >>= 1; + ep_num += 1; + } + } + + // not needed? reception handled in rxflvl + // OUT endpoint interrupt + // if ints.oepint() { + // let mut ep_mask = r.daint().read().oepint(); + // let mut ep_num = 0; + + // while ep_mask != 0 { + // if ep_mask & 1 != 0 { + // let ep_ints = r.doepint(ep_num).read(); + // // clear all + // r.doepint(ep_num).write_value(ep_ints); + // state.ep_out_wakers[ep_num].wake(); + // trace!("out ep={} irq val={=u32:b}", ep_num, ep_ints.0); + // } + + // ep_mask >>= 1; + // ep_num += 1; + // } + // } + } +} + +impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + // TODO: implement VBUS detection + if !self.enabled { + return Poll::Ready(Event::PowerDetected); + } + + let r = T::regs(); + + T::state().bus_waker.register(cx.waker()); + + let ints = unsafe { r.gintsts().read() }; + if ints.usbrst() { + trace!("reset"); + + self.init_fifo(); + self.configure_endpoints(); + + // Reset address + // SAFETY: DCFG is shared with `ControlPipe` so critical section is needed for RMW + critical_section::with(|_| unsafe { + r.dcfg().modify(|w| { + w.set_dad(0); + }); + }); + + // SAFETY: atomic clear on rc_w1 register + unsafe { r.gintsts().write(|w| w.set_usbrst(true)) }; // clear + Self::restore_irqs(); + } + + if ints.enumdne() { + trace!("enumdne"); + + // SAFETY: atomic read with no side effects + let speed = unsafe { r.dsts().read().enumspd() }; + trace!(" speed={}", speed.0); + + // SAFETY: register is only accessed by `Bus` under `&mut self` + unsafe { + r.gusbcfg().modify(|w| { + w.set_trdt(calculate_trdt(speed, T::frequency())); + }) + }; + + // SAFETY: atomic clear on rc_w1 register + unsafe { r.gintsts().write(|w| w.set_enumdne(true)) }; // clear + Self::restore_irqs(); + + return Poll::Ready(Event::Reset); + } + + if ints.usbsusp() { + trace!("suspend"); + // SAFETY: atomic clear on rc_w1 register + unsafe { r.gintsts().write(|w| w.set_usbsusp(true)) }; // clear + Self::restore_irqs(); + return Poll::Ready(Event::Suspend); + } + + if ints.wkupint() { + trace!("resume"); + // SAFETY: atomic clear on rc_w1 register + unsafe { r.gintsts().write(|w| w.set_wkupint(true)) }; // clear + Self::restore_irqs(); + return Poll::Ready(Event::Resume); + } + + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled); + + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_set_stalled index {} out of range", + ep_addr.index() + ); + + let regs = T::regs(); + match ep_addr.direction() { + Direction::Out => { + // SAFETY: DOEPCTL is shared with `Endpoint` so critical section is needed for RMW + critical_section::with(|_| unsafe { + regs.doepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + T::state().ep_out_wakers[ep_addr.index()].wake(); + } + Direction::In => { + // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW + critical_section::with(|_| unsafe { + regs.diepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + T::state().ep_in_wakers[ep_addr.index()].wake(); + } + } + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_is_stalled index {} out of range", + ep_addr.index() + ); + + let regs = T::regs(); + + // SAFETY: atomic read with no side effects + match ep_addr.direction() { + Direction::Out => unsafe { regs.doepctl(ep_addr.index()).read().stall() }, + Direction::In => unsafe { regs.diepctl(ep_addr.index()).read().stall() }, + } + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled); + + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_set_enabled index {} out of range", + ep_addr.index() + ); + + let r = T::regs(); + match ep_addr.direction() { + Direction::Out => { + // SAFETY: DOEPCTL is shared with `Endpoint` so critical section is needed for RMW + critical_section::with(|_| unsafe { + // cancel transfer if active + if !enabled && r.doepctl(ep_addr.index()).read().epena() { + r.doepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); + w.set_epdis(true); + }) + } + + r.doepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + }) + }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_out_wakers[ep_addr.index()].wake(); + } + Direction::In => { + // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW + critical_section::with(|_| unsafe { + // cancel transfer if active + if !enabled && r.diepctl(ep_addr.index()).read().epena() { + r.diepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); + w.set_epdis(true); + }) + } + + r.diepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + }) + }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_in_wakers[ep_addr.index()].wake(); + } + } + } + + async fn enable(&mut self) { + trace!("enable"); + + // SAFETY: registers are only accessed by `Bus` under `&mut self` + unsafe { + #[cfg(stm32l4)] + { + crate::peripherals::PWR::enable(); + critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); + } + + #[cfg(stm32h7)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.cr3().modify(|w| { + w.set_usb33den(true); + w.set_usbregen(internal_regulator); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.cr3().read().usb33rdy() {} + + // Use internal 48MHz HSI clock. Should be enabled in RCC by default. + critical_section::with(|_| { + crate::pac::RCC + .d2ccip2r() + .modify(|w| w.set_usbsel(crate::pac::rcc::vals::Usbsel::HSI48)) + }); + + // Enable ULPI clock if external PHY is used + let ulpien = !self.phy_type.internal(); + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hs_ulpien(ulpien); + } else { + w.set_usb_otg_fs_ulpien(ulpien); + } + }); + crate::pac::RCC.ahb1lpenr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hs_ulpilpen(ulpien); + } else { + w.set_usb_otg_fs_ulpilpen(ulpien); + } + }); + }); + } + + #[cfg(stm32u5)] + { + // Enable USB power + critical_section::with(|_| { + crate::pac::RCC.ahb3enr().modify(|w| { + w.set_pwren(true); + }); + cortex_m::asm::delay(2); + + crate::pac::PWR.svmcr().modify(|w| { + w.set_usv(true); + w.set_uvmen(true); + }); + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.svmsr().read().vddusbrdy() {} + + // Select HSI48 as USB clock source. + critical_section::with(|_| { + crate::pac::RCC.ccipr1().modify(|w| { + w.set_iclksel(crate::pac::rcc::vals::Iclksel::HSI48); + }) + }); + } + + ::enable(); + ::reset(); + + self.irq.set_handler(Self::on_interrupt); + self.irq.unpend(); + self.irq.enable(); + + let r = T::regs(); + let core_id = r.cid().read().0; + info!("Core id {:08x}", core_id); + + // Wait for AHB ready. + while !r.grstctl().read().ahbidl() {} + + // Configure as device. + r.gusbcfg().write(|w| { + // Force device mode + w.set_fdmod(true); + // Enable internal full-speed PHY + w.set_physel(self.phy_type.internal() && !self.phy_type.high_speed()); + }); + + // Configuring Vbus sense and SOF output + match core_id { + 0x0000_1200 | 0x0000_1100 => { + assert!(self.phy_type != PhyType::InternalHighSpeed); + + r.gccfg_v1().modify(|w| { + // Enable internal full-speed PHY, logic is inverted + w.set_pwrdwn(self.phy_type.internal()); + }); + + // F429-like chips have the GCCFG.NOVBUSSENS bit + r.gccfg_v1().modify(|w| { + w.set_novbussens(true); + w.set_vbusasen(false); + w.set_vbusbsen(false); + w.set_sofouten(false); + }); + } + 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => { + // F446-like chips have the GCCFG.VBDEN bit with the opposite meaning + r.gccfg_v2().modify(|w| { + // Enable internal full-speed PHY, logic is inverted + w.set_pwrdwn(self.phy_type.internal() && !self.phy_type.high_speed()); + w.set_phyhsen(self.phy_type.internal() && self.phy_type.high_speed()); + }); + + r.gccfg_v2().modify(|w| { + w.set_vbden(false); + }); + + // Force B-peripheral session + r.gotgctl().modify(|w| { + w.set_bvaloen(true); + w.set_bvaloval(true); + }); + } + _ => unimplemented!("Unknown USB core id {:X}", core_id), + } + + // Soft disconnect. + r.dctl().write(|w| w.set_sdis(true)); + + // Set speed. + r.dcfg().write(|w| { + w.set_pfivl(vals::Pfivl::FRAME_INTERVAL_80); + w.set_dspd(self.phy_type.to_dspd()); + }); + + // Unmask transfer complete EP interrupt + r.diepmsk().write(|w| { + w.set_xfrcm(true); + }); + + // Unmask and clear core interrupts + Bus::::restore_irqs(); + r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF)); + + // Unmask global interrupt + r.gahbcfg().write(|w| { + w.set_gint(true); // unmask global interrupt + }); + + // Connect + r.dctl().write(|w| w.set_sdis(false)); + } + + self.enabled = true; + } + + async fn disable(&mut self) { + trace!("disable"); + + Bus::disable(self); + + self.enabled = false; + } + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +impl<'d, T: Instance> Drop for Bus<'d, T> { + fn drop(&mut self) { + Bus::disable(self); + } +} + +trait Dir { + fn dir() -> Direction; +} + +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, +} + +impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_in_wakers[ep_index].register(cx.waker()); + + // SAFETY: atomic read without side effects + if unsafe { T::regs().diepctl(ep_index).read().usbaep() } { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_out_wakers[ep_index].register(cx.waker()); + + // SAFETY: atomic read without side effects + if unsafe { T::regs().doepctl(ep_index).read().usbaep() } { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("read start len={}", buf.len()); + + poll_fn(|cx| { + let index = self.info.addr.index(); + let state = T::state(); + + state.ep_out_wakers[index].register(cx.waker()); + + let len = state.ep_out_size[index].load(Ordering::Relaxed); + if len != EP_OUT_BUFFER_EMPTY { + trace!("read done len={}", len); + + if len as usize > buf.len() { + return Poll::Ready(Err(EndpointError::BufferOverflow)); + } + + // SAFETY: exclusive access ensured by `ep_out_size` atomic variable + let data = unsafe { core::slice::from_raw_parts(*state.ep_out_buffers[index].get(), len as usize) }; + buf[..len as usize].copy_from_slice(data); + + // Release buffer + state.ep_out_size[index].store(EP_OUT_BUFFER_EMPTY, Ordering::Release); + + // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Bus` so a critical section is needed for RMW + critical_section::with(|_| unsafe { + // Receive 1 packet + T::regs().doeptsiz(index).modify(|w| { + w.set_xfrsiz(self.info.max_packet_size as _); + w.set_pktcnt(1); + }); + + // Clear NAK to indicate we are ready to receive more data + T::regs().doepctl(index).modify(|w| { + w.set_cnak(true); + }); + }); + + Poll::Ready(Ok(len as usize)) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + trace!("write ep={:?} data={:?}", self.info.addr, buf); + + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + let r = T::regs(); + let index = self.info.addr.index(); + let state = T::state(); + + // Wait for previous transfer to complete and check if endpoint is disabled + poll_fn(|cx| { + state.ep_in_wakers[index].register(cx.waker()); + + // SAFETY: atomic read with no side effects + let diepctl = unsafe { r.diepctl(index).read() }; + if !diepctl.usbaep() { + Poll::Ready(Err(EndpointError::Disabled)) + } else if !diepctl.epena() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }) + .await?; + + if buf.len() > 0 { + poll_fn(|cx| { + state.ep_in_wakers[index].register(cx.waker()); + + let size_words = (buf.len() + 3) / 4; + + // SAFETY: atomic read with no side effects + let fifo_space = unsafe { r.dtxfsts(index).read().ineptfsav() as usize }; + if size_words > fifo_space { + // Not enough space in fifo, enable tx fifo empty interrupt + // SAFETY: DIEPEMPMSK is shared with IRQ so critical section is needed for RMW + critical_section::with(|_| unsafe { + r.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() | (1 << index)); + }); + }); + + trace!("tx fifo for ep={} full, waiting for txfe", index); + + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await + } + + // SAFETY: DIEPTSIZ is exclusive to this endpoint under `&mut self` + unsafe { + // Setup transfer size + r.dieptsiz(index).write(|w| { + w.set_mcnt(1); + w.set_pktcnt(1); + w.set_xfrsiz(buf.len() as _); + }); + } + + // SAFETY: DIEPCTL is shared with `Bus` so a critical section is needed for RMW + critical_section::with(|_| unsafe { + // Enable endpoint + r.diepctl(index).modify(|w| { + w.set_cnak(true); + w.set_epena(true); + }); + }); + + // Write data to FIFO + for chunk in buf.chunks(4) { + let mut tmp = [0u8; 4]; + tmp[0..chunk.len()].copy_from_slice(chunk); + // SAFETY: FIFO is exclusive to this endpoint under `&mut self` + unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) }; + } + + trace!("write done ep={:?}", self.info.addr); + + Ok(()) + } +} + +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, + ep_in: Endpoint<'d, T, In>, + ep_out: Endpoint<'d, T, Out>, +} + +impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> { + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } + + async fn setup(&mut self) -> [u8; 8] { + poll_fn(|cx| { + let state = T::state(); + + state.ep_out_wakers[0].register(cx.waker()); + + if state.ep0_setup_ready.load(Ordering::Relaxed) { + let data = unsafe { *state.ep0_setup_data.get() }; + state.ep0_setup_ready.store(false, Ordering::Release); + + // EP0 should not be controlled by `Bus` so this RMW does not need a critical section + unsafe { + // Receive 1 SETUP packet + T::regs().doeptsiz(self.ep_out.info.addr.index()).modify(|w| { + w.set_rxdpid_stupcnt(1); + }); + + // Clear NAK to indicate we are ready to receive more data + T::regs().doepctl(self.ep_out.info.addr.index()).modify(|w| { + w.set_cnak(true); + }); + } + + trace!("SETUP received: {:?}", data); + Poll::Ready(data) + } else { + trace!("SETUP waiting"); + Poll::Pending + } + }) + .await + } + + async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { + trace!("control: data_out"); + let len = self.ep_out.read(buf).await?; + trace!("control: data_out read: {:?}", &buf[..len]); + Ok(len) + } + + async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in write: {:?}", data); + self.ep_in.write(data).await?; + + // wait for status response from host after sending the last packet + if last { + trace!("control: data_in waiting for status"); + self.ep_out.read(&mut []).await?; + trace!("control: complete"); + } + + Ok(()) + } + + async fn accept(&mut self) { + trace!("control: accept"); + + self.ep_in.write(&[]).await.ok(); + + trace!("control: accept OK"); + } + + async fn reject(&mut self) { + trace!("control: reject"); + + // EP0 should not be controlled by `Bus` so this RMW does not need a critical section + unsafe { + let regs = T::regs(); + regs.diepctl(self.ep_in.info.addr.index()).modify(|w| { + w.set_stall(true); + }); + regs.doepctl(self.ep_out.info.addr.index()).modify(|w| { + w.set_stall(true); + }); + } + } + + async fn accept_set_address(&mut self, addr: u8) { + trace!("setting addr: {}", addr); + critical_section::with(|_| unsafe { + T::regs().dcfg().modify(|w| { + w.set_dad(addr); + }); + }); + + // synopsys driver requires accept to be sent after changing address + self.accept().await + } +} + +/// Translates HAL [EndpointType] into PAC [vals::Eptyp] +fn to_eptyp(ep_type: EndpointType) -> vals::Eptyp { + match ep_type { + EndpointType::Control => vals::Eptyp::CONTROL, + EndpointType::Isochronous => vals::Eptyp::ISOCHRONOUS, + EndpointType::Bulk => vals::Eptyp::BULK, + EndpointType::Interrupt => vals::Eptyp::INTERRUPT, + } +} + +/// Calculates total allocated FIFO size in words +fn ep_fifo_size(eps: &[Option]) -> u16 { + eps.iter().map(|ep| ep.map(|ep| ep.fifo_size_words).unwrap_or(0)).sum() +} + +/// Generates IRQ mask for enabled endpoints +fn ep_irq_mask(eps: &[Option]) -> u16 { + eps.iter().enumerate().fold( + 0, + |mask, (index, ep)| { + if ep.is_some() { + mask | (1 << index) + } else { + mask + } + }, + ) +} + +/// Calculates MPSIZ value for EP0, which uses special values. +fn ep0_mpsiz(max_packet_size: u16) -> u16 { + match max_packet_size { + 8 => 0b11, + 16 => 0b10, + 32 => 0b01, + 64 => 0b00, + other => panic!("Unsupported EP0 size: {}", other), + } +} + +fn calculate_trdt(speed: vals::Dspd, ahb_freq: Hertz) -> u8 { + match speed { + vals::Dspd::HIGH_SPEED => { + // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) + if ahb_freq.0 >= 30_000_000 { + 0x9 + } else { + panic!("AHB frequency is too low") + } + } + vals::Dspd::FULL_SPEED_EXTERNAL | vals::Dspd::FULL_SPEED_INTERNAL => { + // From RM0431 (F72xx), RM0090 (F429) + match ahb_freq.0 { + 0..=14_199_999 => panic!("AHB frequency is too low"), + 14_200_000..=14_999_999 => 0xF, + 15_000_000..=15_999_999 => 0xE, + 16_000_000..=17_199_999 => 0xD, + 17_200_000..=18_499_999 => 0xC, + 18_500_000..=19_999_999 => 0xB, + 20_000_000..=21_799_999 => 0xA, + 21_800_000..=23_999_999 => 0x9, + 24_000_000..=27_499_999 => 0x8, + 27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE + 32_000_000..=u32::MAX => 0x6, + } + } + _ => unimplemented!(), + } +} diff --git a/embassy-stm32/src/wdg/mod.rs b/embassy-stm32/src/wdg/mod.rs index 85176eefc..92b9a5ca8 100644 --- a/embassy-stm32/src/wdg/mod.rs +++ b/embassy-stm32/src/wdg/mod.rs @@ -13,12 +13,12 @@ pub struct IndependentWatchdog<'d, T: Instance> { const MAX_RL: u16 = 0xFFF; /// Calculates maximum watchdog timeout in us (RL = 0xFFF) for a given prescaler -const fn max_timeout(prescaler: u8) -> u32 { +const fn max_timeout(prescaler: u16) -> u32 { 1_000_000 * MAX_RL as u32 / (LSI_FREQ.0 / prescaler as u32) } /// Calculates watchdog reload value for the given prescaler and desired timeout -const fn reload_value(prescaler: u8, timeout_us: u32) -> u16 { +const fn reload_value(prescaler: u16, timeout_us: u32) -> u16 { (timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16 } @@ -33,12 +33,12 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> { // Find lowest prescaler value, which makes watchdog period longer or equal to timeout. // This iterates from 4 (2^2) to 256 (2^8). let psc_power = unwrap!((2..=8).find(|psc_power| { - let psc = 2u8.pow(*psc_power); + let psc = 2u16.pow(*psc_power); timeout_us <= max_timeout(psc) })); // Prescaler value - let psc = 2u8.pow(psc_power); + let psc = 2u16.pow(psc_power); // Convert prescaler power to PR register value let pr = psc_power as u8 - 2; diff --git a/embassy-sync/Cargo.toml b/embassy-sync/Cargo.toml index 584d5ba9f..7b5d3ce48 100644 --- a/embassy-sync/Cargo.toml +++ b/embassy-sync/Cargo.toml @@ -19,6 +19,9 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-sync/ features = ["nightly"] target = "thumbv7em-none-eabi" +[package.metadata.docs.rs] +features = ["nightly"] + [features] nightly = ["embedded-io/async"] std = [] @@ -28,11 +31,10 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } futures-util = { version = "0.3.17", default-features = false } -atomic-polyfill = "1.0.1" critical-section = "1.1" heapless = "0.7.5" cfg-if = "1.0.0" -embedded-io = "0.3.0" +embedded-io = "0.4.0" [dev-dependencies] futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index 80bb907a3..f9435ecff 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] #![allow(clippy::new_without_default)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index 7d64b648e..905686acd 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -352,8 +352,6 @@ where mod io_impls { use core::convert::Infallible; - use futures_util::FutureExt; - use super::*; impl embedded_io::Io for Pipe { @@ -361,30 +359,18 @@ mod io_impls { } impl embedded_io::asynch::Read for Pipe { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - Pipe::read(self, buf).map(Ok) + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) } } impl embedded_io::asynch::Write for Pipe { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - Pipe::write(self, buf).map(Ok) + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - futures_util::future::ready(Ok(())) + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } @@ -393,30 +379,18 @@ mod io_impls { } impl embedded_io::asynch::Read for &Pipe { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - Pipe::read(self, buf).map(Ok) + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) } } impl embedded_io::asynch::Write for &Pipe { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - Pipe::write(self, buf).map(Ok) + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - futures_util::future::ready(Ok(())) + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } @@ -425,12 +399,8 @@ mod io_impls { } impl embedded_io::asynch::Read for Reader<'_, M, N> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - Reader::read(self, buf).map(Ok) + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Reader::read(self, buf).await) } } @@ -439,20 +409,12 @@ mod io_impls { } impl embedded_io::asynch::Write for Writer<'_, M, N> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - Writer::write(self, buf).map(Ok) + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Writer::write(self, buf).await) } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - futures_util::future::ready(Ok(())) + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } } diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index c3c10a8af..bea67d8be 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -56,6 +56,15 @@ where } } +impl Default for Signal +where + M: RawMutex, +{ + fn default() -> Self { + Self::new() + } +} + impl Signal where M: RawMutex, diff --git a/embassy-sync/src/waitqueue/waker.rs b/embassy-sync/src/waitqueue/waker.rs index 64e300eb8..9ce94a089 100644 --- a/embassy-sync/src/waitqueue/waker.rs +++ b/embassy-sync/src/waitqueue/waker.rs @@ -6,7 +6,7 @@ use crate::blocking_mutex::raw::CriticalSectionRawMutex; use crate::blocking_mutex::Mutex; /// Utility struct to register and wake a waker. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct WakerRegistration { waker: Option, } diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index c51a71d01..5701ab351 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -2,8 +2,16 @@ name = "embassy-time" version = "0.1.0" edition = "2021" +description = "Instant and Duration for embedded no-std systems, with async timer support" +repository = "https://github.com/embassy-rs/embassy" +readme = "README.md" license = "MIT OR Apache-2.0" - +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-v$VERSION/embassy-time/src/" @@ -11,6 +19,9 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time/ features = ["nightly", "defmt", "unstable-traits", "std"] target = "x86_64-unknown-linux-gnu" +[package.metadata.docs.rs] +features = ["nightly", "defmt", "unstable-traits", "std"] + [features] std = ["tick-hz-1_000_000"] wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000"] @@ -26,6 +37,22 @@ unstable-traits = ["embedded-hal-1"] # To use this you must have a time driver provided. defmt-timestamp-uptime = ["defmt"] +# Create a global, generic queue that can be used with any executor +# To use this you must have a time driver provided. +generic-queue = [] + +# Set the number of timers for the generic queue. +# +# At most 1 `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used. +# +# When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the +# end user to pick. +generic-queue-8 = ["generic-queue"] +generic-queue-16 = ["generic-queue"] +generic-queue-32 = ["generic-queue"] +generic-queue-64 = ["generic-queue"] +generic-queue-128 = ["generic-queue"] + # Set the `embassy_time` tick rate. # # At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. @@ -107,15 +134,21 @@ log = { version = "0.4.14", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9", optional = true} -embedded-hal-async = { version = "=0.1.0-alpha.2", optional = true} +embedded-hal-async = { version = "=0.2.0-alpha.0", optional = true} futures-util = { version = "0.3.17", default-features = false } -embassy-macros = { version = "0.1.0", path = "../embassy-macros"} +embassy-sync = { version = "0.1", path = "../embassy-sync" } atomic-polyfill = "1.0.1" critical-section = "1.1" cfg-if = "1.0.0" +heapless = "0.7" # WASM dependencies wasm-bindgen = { version = "0.2.81", optional = true } js-sys = { version = "0.3", optional = true } wasm-timer = { version = "0.2.5", optional = true } + +[dev-dependencies] +serial_test = "0.9" +critical-section = { version = "1.1", features = ["std"] } + diff --git a/embassy-time/src/delay.rs b/embassy-time/src/delay.rs index ff6b6869a..0ca176abd 100644 --- a/embassy-time/src/delay.rs +++ b/embassy-time/src/delay.rs @@ -33,26 +33,18 @@ mod eh1 { #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eha { - use core::future::Future; - - use futures_util::FutureExt; - use super::*; use crate::Timer; impl embedded_hal_async::delay::DelayUs for Delay { type Error = core::convert::Infallible; - type DelayUsFuture<'a> = impl Future> + 'a where Self: 'a; - - fn delay_us(&mut self, micros: u32) -> Self::DelayUsFuture<'_> { - Timer::after(Duration::from_micros(micros as _)).map(Ok) + async fn delay_us(&mut self, micros: u32) -> Result<(), Self::Error> { + Ok(Timer::after(Duration::from_micros(micros as _)).await) } - type DelayMsFuture<'a> = impl Future> + 'a where Self: 'a; - - fn delay_ms(&mut self, millis: u32) -> Self::DelayMsFuture<'_> { - Timer::after(Duration::from_millis(millis as _)).map(Ok) + async fn delay_ms(&mut self, millis: u32) -> Result<(), Self::Error> { + Ok(Timer::after(Duration::from_millis(millis as _)).await) } } } diff --git a/embassy-time/src/driver.rs b/embassy-time/src/driver.rs index 79ae14b91..5c2ad3b23 100644 --- a/embassy-time/src/driver.rs +++ b/embassy-time/src/driver.rs @@ -105,20 +105,21 @@ pub trait Driver: Send + Sync + 'static { /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm /// timestamp, the provided callback function will be called. /// - /// If `timestamp` is already in the past, the alarm callback must be immediately fired. - /// In this case, it is allowed (but not mandatory) to call the alarm callback synchronously from `set_alarm`. + /// 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. /// /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. /// /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any. - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64); + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool; } extern "Rust" { fn _embassy_time_now() -> u64; fn _embassy_time_allocate_alarm() -> Option; fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64); + fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool; } /// See [`Driver::now`] @@ -139,7 +140,7 @@ pub fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ( } /// See [`Driver::set_alarm`] -pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) { +pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool { unsafe { _embassy_time_set_alarm(alarm, timestamp) } } @@ -167,7 +168,7 @@ macro_rules! time_driver_impl { } #[no_mangle] - fn _embassy_time_set_alarm(alarm: $crate::driver::AlarmHandle, timestamp: u64) { + fn _embassy_time_set_alarm(alarm: $crate::driver::AlarmHandle, timestamp: u64) -> bool { <$t as $crate::driver::Driver>::set_alarm(&$name, alarm, timestamp) } }; diff --git a/embassy-time/src/driver_std.rs b/embassy-time/src/driver_std.rs index 2ddb2e604..da46a599d 100644 --- a/embassy-time/src/driver_std.rs +++ b/embassy-time/src/driver_std.rs @@ -1,10 +1,12 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::mem::MaybeUninit; use std::sync::{Condvar, Mutex, Once}; use std::time::{Duration as StdDuration, Instant as StdInstant}; use std::{mem, ptr, thread}; use atomic_polyfill::{AtomicU8, Ordering}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex as EmbassyMutex; use crate::driver::{AlarmHandle, Driver}; @@ -35,7 +37,10 @@ struct TimeDriver { alarm_count: AtomicU8, once: Once, - alarms: UninitCell>, + // The STD Driver implementation requires the alarms' mutex to be reentrant, which the STD Mutex isn't + // Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections + // themselves are reentrant + alarms: UninitCell>>, zero_instant: UninitCell, signaler: UninitCell, } @@ -53,7 +58,8 @@ crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { impl TimeDriver { fn init(&self) { self.once.call_once(|| unsafe { - self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); + self.alarms + .write(EmbassyMutex::new(RefCell::new([ALARM_NEW; ALARM_COUNT]))); self.zero_instant.write(StdInstant::now()); self.signaler.write(Signaler::new()); @@ -66,25 +72,37 @@ impl TimeDriver { loop { let now = DRIVER.now(); - let mut next_alarm = u64::MAX; - { - let alarms = &mut *unsafe { DRIVER.alarms.as_ref() }.lock().unwrap(); - for alarm in alarms { - if alarm.timestamp <= now { - alarm.timestamp = u64::MAX; + let next_alarm = unsafe { DRIVER.alarms.as_ref() }.lock(|alarms| { + loop { + let pending = alarms + .borrow_mut() + .iter_mut() + .find(|alarm| alarm.timestamp <= now) + .map(|alarm| { + alarm.timestamp = u64::MAX; - // Call after clearing alarm, so the callback can set another alarm. + (alarm.callback, alarm.ctx) + }); + if let Some((callback, ctx)) = pending { // safety: // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback) }; - f(alarm.ctx); + let f: fn(*mut ()) = unsafe { mem::transmute(callback) }; + f(ctx); } else { - next_alarm = next_alarm.min(alarm.timestamp); + // No alarm due + break; } } - } + + alarms + .borrow() + .iter() + .map(|alarm| alarm.timestamp) + .min() + .unwrap_or(u64::MAX) + }); // Ensure we don't overflow let until = zero @@ -121,18 +139,25 @@ impl Driver for TimeDriver { fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.callback = callback as *const (); - alarm.ctx = ctx; + unsafe { self.alarms.as_ref() }.lock(|alarms| { + let mut alarms = alarms.borrow_mut(); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.callback = callback as *const (); + alarm.ctx = ctx; + }); } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.timestamp = timestamp; - unsafe { self.signaler.as_ref() }.signal(); + unsafe { self.alarms.as_ref() }.lock(|alarms| { + let mut alarms = alarms.borrow_mut(); + + let alarm = &mut alarms[alarm.id() as usize]; + alarm.timestamp = timestamp; + unsafe { self.signaler.as_ref() }.signal(); + }); + + true } } diff --git a/embassy-time/src/driver_wasm.rs b/embassy-time/src/driver_wasm.rs index e4497e6a2..63d049897 100644 --- a/embassy-time/src/driver_wasm.rs +++ b/embassy-time/src/driver_wasm.rs @@ -90,15 +90,23 @@ impl Driver for TimeDriver { })); } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { self.init(); let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); let alarm = &mut alarms[alarm.id() as usize]; - let timeout = (timestamp - self.now()) as u32; if let Some(token) = alarm.token { clearTimeout(token); } - alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); + + let now = self.now(); + if timestamp <= now { + false + } else { + let timeout = (timestamp - now) as u32; + alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); + + true + } } } diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs index d3c6f42a9..9d0bab2dd 100644 --- a/embassy-time/src/duration.rs +++ b/embassy-time/src/duration.rs @@ -81,6 +81,20 @@ impl Duration { } } + /// Creates a duration corresponding to the specified Hz. + /// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1 + /// tick. Doing so will not deadlock, but will certainly not produce the desired output. + pub const fn from_hz(hz: u64) -> Duration { + let ticks = { + if hz >= TICK_HZ { + 1 + } else { + (TICK_HZ + hz / 2) / hz + } + }; + Duration { ticks } + } + /// Adds one Duration to another, returning a new Duration or None in the event of an overflow. pub fn checked_add(self, rhs: Duration) -> Option { self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks }) diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs index 4edc883fe..8b0aebe19 100644 --- a/embassy-time/src/lib.rs +++ b/embassy-time/src/lib.rs @@ -1,5 +1,6 @@ -#![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] +#![cfg_attr(not(any(feature = "std", feature = "wasm", test)), no_std)] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] #![doc = include_str!("../README.md")] #![allow(clippy::new_without_default)] #![warn(missing_docs)] @@ -11,6 +12,7 @@ mod delay; pub mod driver; mod duration; mod instant; +pub mod queue; mod tick; mod timer; @@ -18,6 +20,8 @@ mod timer; mod driver_std; #[cfg(feature = "wasm")] mod driver_wasm; +#[cfg(feature = "generic-queue")] +mod queue_generic; pub use delay::{block_for, Delay}; pub use duration::Duration; diff --git a/embassy-time/src/queue.rs b/embassy-time/src/queue.rs new file mode 100644 index 000000000..c6f8b440a --- /dev/null +++ b/embassy-time/src/queue.rs @@ -0,0 +1,58 @@ +//! Timer queue implementation +//! +//! This module defines the interface a timer queue needs to implement to power the `embassy_time` module. +//! +//! # Implementing a timer queue +//! +//! - Define a struct `MyTimerQueue` +//! - Implement [`TimerQueue`] for it +//! - Register it as the global timer queue with [`timer_queue_impl`](crate::timer_queue_impl). +//! +//! # Linkage details +//! +//! Check the documentation of the [`driver`](crate::driver) module for more information. +//! +//! Similarly to driver, if there is none or multiple timer queues in the crate tree, linking will fail. +//! +//! # Example +//! +//! ``` +//! use core::task::Waker; +//! +//! use embassy_time::Instant; +//! use embassy_time::queue::{TimerQueue}; +//! +//! struct MyTimerQueue{}; // not public! +//! embassy_time::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{}); +//! +//! impl TimerQueue for MyTimerQueue { +//! fn schedule_wake(&'static self, at: Instant, waker: &Waker) { +//! todo!() +//! } +//! } +//! ``` +use core::task::Waker; + +use crate::Instant; + +/// Timer queue +pub trait TimerQueue { + /// Schedules a waker in the queue to be awoken at moment `at`. + /// If this moment is in the past, the waker might be awoken immediately. + fn schedule_wake(&'static self, at: Instant, waker: &Waker); +} + +/// Set the TimerQueue implementation. +/// +/// See the module documentation for an example. +#[macro_export] +macro_rules! timer_queue_impl { + (static $name:ident: $t: ty = $val:expr) => { + static $name: $t = $val; + + #[no_mangle] + fn _embassy_time_schedule_wake(at: $crate::Instant, waker: &core::task::Waker) { + <$t as $crate::queue::TimerQueue>::schedule_wake(&$name, at, waker); + } + }; +} diff --git a/embassy-time/src/queue_generic.rs b/embassy-time/src/queue_generic.rs new file mode 100644 index 000000000..20ae7e6cc --- /dev/null +++ b/embassy-time/src/queue_generic.rs @@ -0,0 +1,449 @@ +use core::cell::RefCell; +use core::cmp::{min, Ordering}; +use core::task::Waker; + +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use heapless::Vec; + +use crate::driver::{allocate_alarm, set_alarm, set_alarm_callback, AlarmHandle}; +use crate::queue::TimerQueue; +use crate::Instant; + +#[cfg(feature = "generic-queue-8")] +const QUEUE_SIZE: usize = 8; +#[cfg(feature = "generic-queue-16")] +const QUEUE_SIZE: usize = 16; +#[cfg(feature = "generic-queue-32")] +const QUEUE_SIZE: usize = 32; +#[cfg(feature = "generic-queue-64")] +const QUEUE_SIZE: usize = 32; +#[cfg(feature = "generic-queue-128")] +const QUEUE_SIZE: usize = 128; +#[cfg(not(any( + feature = "generic-queue-8", + feature = "generic-queue-16", + feature = "generic-queue-32", + feature = "generic-queue-64", + feature = "generic-queue-128" +)))] +const QUEUE_SIZE: usize = 64; + +#[derive(Debug)] +struct Timer { + at: Instant, + waker: Waker, +} + +impl PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + self.at == other.at + } +} + +impl Eq for Timer {} + +impl PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + self.at.partial_cmp(&other.at) + } +} + +impl Ord for Timer { + fn cmp(&self, other: &Self) -> Ordering { + self.at.cmp(&other.at) + } +} + +struct InnerQueue { + queue: Vec, + alarm: AlarmHandle, +} + +impl InnerQueue { + fn schedule_wake(&mut self, at: Instant, waker: &Waker) { + self.queue + .iter_mut() + .find(|timer| timer.waker.will_wake(waker)) + .map(|mut timer| { + timer.at = min(timer.at, at); + }) + .unwrap_or_else(|| { + let mut timer = Timer { + waker: waker.clone(), + at, + }; + + loop { + match self.queue.push(timer) { + Ok(()) => break, + Err(e) => timer = e, + } + + self.queue.pop().unwrap().waker.wake(); + } + }); + + // Don't wait for the alarm callback to trigger and directly + // dispatch all timers that are already due + // + // Then update the alarm if necessary + self.dispatch(); + } + + fn dispatch(&mut self) { + loop { + let now = Instant::now(); + + let mut next_alarm = Instant::MAX; + + let mut i = 0; + while i < self.queue.len() { + let timer = &self.queue[i]; + if timer.at <= now { + let timer = self.queue.swap_remove(i); + timer.waker.wake(); + } else { + next_alarm = min(next_alarm, timer.at); + i += 1; + } + } + + if self.update_alarm(next_alarm) { + break; + } + } + } + + fn update_alarm(&mut self, next_alarm: Instant) -> bool { + if next_alarm == Instant::MAX { + true + } else { + set_alarm(self.alarm, next_alarm.as_ticks()) + } + } + + fn handle_alarm(&mut self) { + self.dispatch(); + } +} + +struct Queue { + inner: Mutex>>, +} + +impl Queue { + const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(None)), + } + } + + fn schedule_wake(&'static self, at: Instant, waker: &Waker) { + self.inner.lock(|inner| { + let mut inner = inner.borrow_mut(); + + if inner.is_none() {} + + inner + .get_or_insert_with(|| { + let handle = unsafe { allocate_alarm() }.unwrap(); + set_alarm_callback(handle, Self::handle_alarm_callback, self as *const _ as _); + InnerQueue { + queue: Vec::new(), + alarm: handle, + } + }) + .schedule_wake(at, waker) + }); + } + + fn handle_alarm(&self) { + self.inner + .lock(|inner| inner.borrow_mut().as_mut().unwrap().handle_alarm()); + } + + fn handle_alarm_callback(ctx: *mut ()) { + unsafe { (ctx as *const Self).as_ref().unwrap() }.handle_alarm(); + } +} + +impl TimerQueue for Queue { + fn schedule_wake(&'static self, at: Instant, waker: &Waker) { + Queue::schedule_wake(self, at, waker); + } +} + +crate::timer_queue_impl!(static QUEUE: Queue = Queue::new()); + +#[cfg(test)] +mod tests { + use core::cell::Cell; + use core::task::{RawWaker, RawWakerVTable, Waker}; + use std::rc::Rc; + use std::sync::Mutex; + + use serial_test::serial; + + use super::InnerQueue; + use crate::driver::{AlarmHandle, Driver}; + use crate::queue_generic::QUEUE; + use crate::Instant; + + struct InnerTestDriver { + now: u64, + alarm: u64, + callback: fn(*mut ()), + ctx: *mut (), + } + + impl InnerTestDriver { + const fn new() -> Self { + Self { + now: 0, + alarm: u64::MAX, + callback: Self::noop, + ctx: core::ptr::null_mut(), + } + } + + fn noop(_ctx: *mut ()) {} + } + + unsafe impl Send for InnerTestDriver {} + + struct TestDriver(Mutex); + + impl TestDriver { + const fn new() -> Self { + Self(Mutex::new(InnerTestDriver::new())) + } + + fn reset(&self) { + *self.0.lock().unwrap() = InnerTestDriver::new(); + } + + fn set_now(&self, now: u64) { + let notify = { + let mut inner = self.0.lock().unwrap(); + + if inner.now < now { + inner.now = now; + + if inner.alarm <= now { + inner.alarm = u64::MAX; + + Some((inner.callback, inner.ctx)) + } else { + None + } + } else { + panic!("Going back in time?"); + } + }; + + if let Some((callback, ctx)) = notify { + (callback)(ctx); + } + } + } + + impl Driver for TestDriver { + fn now(&self) -> u64 { + self.0.lock().unwrap().now + } + + unsafe fn allocate_alarm(&self) -> Option { + Some(AlarmHandle::new(0)) + } + + fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + let mut inner = self.0.lock().unwrap(); + + inner.callback = callback; + inner.ctx = ctx; + } + + fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool { + let mut inner = self.0.lock().unwrap(); + + if timestamp <= inner.now { + false + } else { + inner.alarm = timestamp; + true + } + } + } + + struct TestWaker { + pub awoken: Rc>, + pub waker: Waker, + } + + impl TestWaker { + fn new() -> Self { + let flag = Rc::new(Cell::new(false)); + + const VTABLE: RawWakerVTable = RawWakerVTable::new( + |data: *const ()| { + unsafe { + Rc::increment_strong_count(data as *const Cell); + } + + RawWaker::new(data as _, &VTABLE) + }, + |data: *const ()| unsafe { + let data = data as *const Cell; + data.as_ref().unwrap().set(true); + Rc::decrement_strong_count(data); + }, + |data: *const ()| unsafe { + (data as *const Cell).as_ref().unwrap().set(true); + }, + |data: *const ()| unsafe { + Rc::decrement_strong_count(data); + }, + ); + + let raw = RawWaker::new(Rc::into_raw(flag.clone()) as _, &VTABLE); + + Self { + awoken: flag.clone(), + waker: unsafe { Waker::from_raw(raw) }, + } + } + } + + crate::time_driver_impl!(static DRIVER: TestDriver = TestDriver::new()); + + fn setup() { + DRIVER.reset(); + + QUEUE.inner.lock(|inner| { + *inner.borrow_mut() = InnerQueue::new(); + }); + } + + fn queue_len() -> usize { + QUEUE.inner.lock(|inner| inner.borrow().queue.iter().count()) + } + + #[test] + #[serial] + fn test_schedule() { + setup(); + + assert_eq!(queue_len(), 0); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(1), &waker.waker); + + assert!(!waker.awoken.get()); + assert_eq!(queue_len(), 1); + } + + #[test] + #[serial] + fn test_schedule_same() { + setup(); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(1), &waker.waker); + + assert_eq!(queue_len(), 1); + + QUEUE.schedule_wake(Instant::from_secs(1), &waker.waker); + + assert_eq!(queue_len(), 1); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker); + + assert_eq!(queue_len(), 1); + + let waker2 = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker2.waker); + + assert_eq!(queue_len(), 2); + } + + #[test] + #[serial] + fn test_trigger() { + setup(); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker); + + assert!(!waker.awoken.get()); + + DRIVER.set_now(Instant::from_secs(99).as_ticks()); + + assert!(!waker.awoken.get()); + + assert_eq!(queue_len(), 1); + + DRIVER.set_now(Instant::from_secs(100).as_ticks()); + + assert!(waker.awoken.get()); + + assert_eq!(queue_len(), 0); + } + + #[test] + #[serial] + fn test_immediate_trigger() { + setup(); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker); + + DRIVER.set_now(Instant::from_secs(50).as_ticks()); + + let waker2 = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(40), &waker2.waker); + + assert!(!waker.awoken.get()); + assert!(waker2.awoken.get()); + assert_eq!(queue_len(), 1); + } + + #[test] + #[serial] + fn test_queue_overflow() { + setup(); + + for i in 1..super::QUEUE_SIZE { + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(310), &waker.waker); + + assert_eq!(queue_len(), i); + assert!(!waker.awoken.get()); + } + + let first_waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(300), &first_waker.waker); + + assert_eq!(queue_len(), super::QUEUE_SIZE); + assert!(!first_waker.awoken.get()); + + let second_waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(305), &second_waker.waker); + + assert_eq!(queue_len(), super::QUEUE_SIZE); + assert!(first_waker.awoken.get()); + + QUEUE.schedule_wake(Instant::from_secs(320), &TestWaker::new().waker); + assert_eq!(queue_len(), super::QUEUE_SIZE); + assert!(second_waker.awoken.get()); + } +} diff --git a/embassy-usb-driver/Cargo.toml b/embassy-usb-driver/Cargo.toml index d22bf7d72..d658f9ec7 100644 --- a/embassy-usb-driver/Cargo.toml +++ b/embassy-usb-driver/Cargo.toml @@ -14,4 +14,3 @@ target = "thumbv7em-none-eabi" [dependencies] defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } diff --git a/embassy-usb-driver/README.md b/embassy-usb-driver/README.md new file mode 100644 index 000000000..012663c3f --- /dev/null +++ b/embassy-usb-driver/README.md @@ -0,0 +1,32 @@ +# embassy-usb-driver + +This crate contains the driver traits for [`embassy-usb`]. HAL/BSP crates can implement these +traits to add support for using `embassy-usb` for a given chip/platform. + +The traits are kept in a separate crate so that breaking changes in the higher-level [`embassy-usb`] +APIs don't cause a semver-major bump of thsi crate. This allows existing HALs/BSPs to be used +with the newer `embassy-usb` without needing updates. + +If you're writing an application using USB, you should depend on the main [`embassy-usb`] crate +instead of this one. + +[`embassy-usb`]: https://crates.io/crates/embassy-usb + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs index 931e9c318..e8f71a2dc 100644 --- a/embassy-usb-driver/src/lib.rs +++ b/embassy-usb-driver/src/lib.rs @@ -1,6 +1,8 @@ #![no_std] - -use core::future::Future; +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] /// Direction of USB traffic. Note that in the USB standard the direction is always indicated from /// the perspective of the host, which is backward for devices, but the standard directions are used @@ -95,46 +97,65 @@ impl EndpointAddress { } } +/// Infomation for an endpoint. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointInfo { + /// Endpoint's address. pub addr: EndpointAddress, + /// Endpoint's type. pub ep_type: EndpointType, + /// Max packet size, in bytes. pub max_packet_size: u16, - pub interval: u8, + /// Polling interval, in milliseconds. + pub interval_ms: u8, } -/// Driver for a specific USB peripheral. Implement this to add support for a new hardware -/// platform. +/// Main USB driver trait. +/// +/// Implement this to add support for a new hardware platform. pub trait Driver<'a> { + /// Type of the OUT endpoints for this driver. type EndpointOut: EndpointOut + 'a; + /// Type of the IN endpoints for this driver. type EndpointIn: EndpointIn + 'a; + /// Type of the control pipe for this driver. type ControlPipe: ControlPipe + 'a; + /// Type for bus control for this driver. type Bus: Bus + 'a; - /// Allocates an endpoint and specified endpoint parameters. This method is called by the device - /// and class implementations to allocate endpoints, and can only be called before - /// [`start`](Self::start) is called. + /// Allocates an OUT endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. /// /// # Arguments /// - /// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should - /// attempt to return an endpoint with the specified address. If None, the implementation - /// should return the next available one. + /// * `ep_type` - the endpoint's type. /// * `max_packet_size` - Maximum packet size in bytes. - /// * `interval` - Polling interval parameter for interrupt endpoints. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result; + /// Allocates an IN endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. fn alloc_endpoint_in( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result; /// Start operation of the USB device. @@ -146,148 +167,187 @@ pub trait Driver<'a> { /// This consumes the `Driver` instance, so it's no longer possible to allocate more /// endpoints. fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); - - /// Indicates that `set_device_address` must be called before accepting the corresponding - /// control transfer, not after. - /// - /// The default value for this constant is `false`, which corresponds to the USB 2.0 spec, 9.4.6 - const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; } +/// USB bus trait. +/// +/// This trait provides methods that act on the whole bus. It is kept owned by +/// the main USB task, and used to manage the bus. pub trait Bus { - type EnableFuture<'a>: Future + 'a - where - Self: 'a; - type DisableFuture<'a>: Future + 'a - where - Self: 'a; - type PollFuture<'a>: Future + 'a - where - Self: 'a; - type RemoteWakeupFuture<'a>: Future> + 'a - where - Self: 'a; + /// Enable the USB peripheral. + async fn enable(&mut self); - /// Enables the USB peripheral. Soon after enabling the device will be reset, so - /// there is no need to perform a USB reset in this method. - fn enable(&mut self) -> Self::EnableFuture<'_>; + /// Disable and powers down the USB peripheral. + async fn disable(&mut self); - /// Disables and powers down the USB peripheral. - fn disable(&mut self) -> Self::DisableFuture<'_>; + /// Wait for a bus-related event. + /// + /// This method should asynchronously wait for an event to happen, then + /// return it. See [`Event`] for the list of events this method should return. + async fn poll(&mut self) -> Event; - fn poll<'a>(&'a mut self) -> Self::PollFuture<'a>; - - /// Sets the device USB address to `addr`. - fn set_address(&mut self, addr: u8); - - /// Enables or disables an endpoint. + /// Enable or disable an endpoint. fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); - /// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it - /// should be prepared to receive data again. Only used during control transfers. + /// Set or clear the STALL condition for an endpoint. + /// + /// If the endpoint is an OUT endpoint, it should be prepared to receive data again. fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); - /// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. + /// Get whether the STALL condition is set for an endpoint. fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; - /// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the + /// Simulate a disconnect from the USB bus, causing the host to reset and re-enumerate the /// device. /// /// The default implementation just returns `Unsupported`. /// /// # Errors /// - /// * [`Unsupported`](crate::driver::Unsupported) - This UsbBus implementation doesn't support + /// * [`Unsupported`](crate::Unsupported) - This UsbBus implementation doesn't support /// simulating a disconnect or it has not been enabled at creation time. fn force_reset(&mut self) -> Result<(), Unsupported> { Err(Unsupported) } - /// Initiates a remote wakeup of the host by the device. + /// Initiate a remote wakeup of the host by the device. /// /// # Errors /// - /// * [`Unsupported`](crate::driver::Unsupported) - This UsbBus implementation doesn't support + /// * [`Unsupported`](crate::Unsupported) - This UsbBus implementation doesn't support /// remote wakeup or it has not been enabled at creation time. - fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_>; + async fn remote_wakeup(&mut self) -> Result<(), Unsupported>; } +/// Endpoint trait, common for OUT and IN. pub trait Endpoint { - type WaitEnabledFuture<'a>: Future + 'a - where - Self: 'a; - /// Get the endpoint address fn info(&self) -> &EndpointInfo; - /// Waits for the endpoint to be enabled. - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_>; + /// Wait for the endpoint to be enabled. + async fn wait_enabled(&mut self); } +/// OUT Endpoint trait. pub trait EndpointOut: Endpoint { - type ReadFuture<'a>: Future> + 'a - where - Self: 'a; - - /// Reads a single packet of data from the endpoint, and returns the actual length of + /// Read a single packet of data from the endpoint, and return the actual length of /// the packet. /// /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; + async fn read(&mut self, buf: &mut [u8]) -> Result; } +/// USB control pipe trait. +/// +/// The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single +/// unit, and manages them together to implement the control pipe state machine. +/// +/// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that +/// many USB peripherals treat the control pipe endpoints differently (different registers, +/// different procedures), usually to accelerate processing in hardware somehow. A separate +/// trait allows the driver to handle it specially. +/// +/// The call sequences made by the USB stack to the ControlPipe are the following: +/// +/// - control in/out with len=0: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept() or reject() +/// ``` +/// +/// - control out for setting the device address: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept_set_address(addr) or reject() +/// ``` +/// +/// - control out with len != 0: +/// +/// ```not_rust +/// setup() +/// data_out(first=true, last=false) +/// data_out(first=false, last=false) +/// ... +/// data_out(first=false, last=false) +/// data_out(first=false, last=true) +/// (...processing...) +/// accept() or reject() +/// ``` +/// +/// - control in with len != 0, accepted: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// data_in(first=true, last=false) +/// data_in(first=false, last=false) +/// ... +/// data_in(first=false, last=false) +/// data_in(first=false, last=true) +/// (NO `accept()`!!! This is because calling `data_in` already implies acceptance.) +/// ``` +/// +/// - control in with len != 0, rejected: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// reject() +/// ``` +/// +/// The driver is responsible for handling the status stage. The stack DOES NOT do zero-length +/// calls to `data_in` or `data_out` for the status zero-length packet. The status stage should +/// be triggered by either `accept()`, or `data_in` with `last = true`. +/// +/// Note that the host can abandon a control request and send a new SETUP packet any time. If +/// a SETUP packet arrives at any time during `data_out`, `data_in`, `accept` or `reject`, +/// the driver must immediately return (with `EndpointError::Disabled` from `data_in`, `data_out`) +/// to let the stack call `setup()` again to start handling the new control request. Not doing +/// so will cause things to get stuck, because the host will never read/send the packet we're +/// waiting for. pub trait ControlPipe { - type SetupFuture<'a>: Future + 'a - where - Self: 'a; - type DataOutFuture<'a>: Future> + 'a - where - Self: 'a; - type DataInFuture<'a>: Future> + 'a - where - Self: 'a; - type AcceptFuture<'a>: Future + 'a - where - Self: 'a; - type RejectFuture<'a>: Future + 'a - where - Self: 'a; - /// Maximum packet size for the control pipe fn max_packet_size(&self) -> usize; - /// Reads a single setup packet from the endpoint. - fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a>; + /// Read a single setup packet from the endpoint. + async fn setup(&mut self) -> [u8; 8]; - /// Reads a DATA OUT packet into `buf` in response to a control write request. + /// Read a DATA OUT packet into `buf` in response to a control write request. /// /// Must be called after `setup()` for requests with `direction` of `Out` /// and `length` greater than zero. - fn data_out<'a>(&'a mut self, buf: &'a mut [u8], first: bool, last: bool) -> Self::DataOutFuture<'a>; + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result; - /// Sends a DATA IN packet with `data` in response to a control read request. + /// Send a DATA IN packet with `data` in response to a control read request. /// /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. - fn data_in<'a>(&'a mut self, data: &'a [u8], first: bool, last: bool) -> Self::DataInFuture<'a>; + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>; - /// Accepts a control request. + /// Accept a control request. /// /// Causes the STATUS packet for the current request to be ACKed. - fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a>; + async fn accept(&mut self); - /// Rejects a control request. + /// Reject a control request. /// /// Sets a STALL condition on the pipe to indicate an error. - fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a>; + async fn reject(&mut self); + + /// Accept SET_ADDRESS control and change bus address. + /// + /// For most drivers this function should firstly call `accept()` and then change the bus address. + /// However, there are peripherals (Synopsys USB OTG) that have reverse order. + async fn accept_set_address(&mut self, addr: u8); } +/// IN Endpoint trait. pub trait EndpointIn: Endpoint { - type WriteFuture<'a>: Future> + 'a - where - Self: 'a; - - /// Writes a single packet of data to the endpoint. - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a>; + /// Write a single packet of data to the endpoint. + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>; } #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -312,18 +372,22 @@ pub enum Event { PowerRemoved, } +/// Allocating an endpoint failed. +/// +/// This can be due to running out of endpoints, or out of endpoint memory, +/// or because the hardware doesn't support the requested combination of features. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointAllocError; +/// Operation is unsupported by the driver. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Operation is unsupported by the driver. pub struct Unsupported; +/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] pub enum EndpointError { /// Either the packet to be written is too long to fit in the transmission /// buffer or the received packet is too long to fit in `buf`. diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml new file mode 100644 index 000000000..18d70b0c5 --- /dev/null +++ b/embassy-usb-logger/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "embassy-usb-logger" +version = "0.1.0" +edition = "2021" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-logger-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-logger/src/" +target = "thumbv7em-none-eabi" + +[dependencies] +embassy-usb = { version = "0.1.0", path = "../embassy-usb" } +embassy-sync = { version = "0.1.0", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +futures = { version = "0.3", default-features = false } +static_cell = "1" +usbd-hid = "0.6.0" +log = "0.4" diff --git a/embassy-usb-logger/README.md b/embassy-usb-logger/README.md new file mode 100644 index 000000000..81b0dcd0e --- /dev/null +++ b/embassy-usb-logger/README.md @@ -0,0 +1,15 @@ +# embassy-usb-logger + +USB implementation of the `log` crate. This logger can be used by any device that implements `embassy-usb`. When running, +it will output all logging done through the `log` facade to the USB serial peripheral. + +## Usage + +Add the following embassy task to your application. The `Driver` type is different depending on which HAL you use. + + ```rust +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +} +``` diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs new file mode 100644 index 000000000..1d8dd13ce --- /dev/null +++ b/embassy-usb-logger/src/lib.rs @@ -0,0 +1,145 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +use core::fmt::Write as _; + +use embassy_futures::join::join; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::Driver; +use embassy_usb::{Builder, Config}; +use log::{Metadata, Record}; + +type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; + +/// The logger state containing buffers that must live as long as the USB peripheral. +pub struct LoggerState<'d> { + state: State<'d>, + device_descriptor: [u8; 32], + config_descriptor: [u8; 128], + bos_descriptor: [u8; 16], + control_buf: [u8; 64], +} + +impl<'d> LoggerState<'d> { + /// Create a new instance of the logger state. + pub fn new() -> Self { + Self { + state: State::new(), + device_descriptor: [0; 32], + config_descriptor: [0; 128], + bos_descriptor: [0; 16], + control_buf: [0; 64], + } + } +} + +/// The logger handle, which contains a pipe with configurable size for buffering log messages. +pub struct UsbLogger { + buffer: Pipe, +} + +impl UsbLogger { + /// Create a new logger instance. + pub const fn new() -> Self { + Self { buffer: Pipe::new() } + } + + /// Run the USB logger using the state and USB driver. Never returns. + pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> ! + where + D: Driver<'d>, + Self: 'd, + { + const MAX_PACKET_SIZE: u8 = 64; + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial logger"); + config.serial_number = None; + config.max_power = 100; + config.max_packet_size_0 = MAX_PACKET_SIZE; + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + let mut builder = Builder::new( + driver, + config, + &mut state.device_descriptor, + &mut state.config_descriptor, + &mut state.bos_descriptor, + &mut state.control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16); + + // Build the builder. + let mut device = builder.build(); + + loop { + let run_fut = device.run(); + let log_fut = async { + let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + class.wait_connection().await; + loop { + let len = self.buffer.read(&mut rx[..]).await; + let _ = class.write_packet(&rx[..len]).await; + } + }; + join(run_fut, log_fut).await; + } + } +} + +impl log::Log for UsbLogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + } + } + + fn flush(&self) {} +} + +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> { + let _ = self.0.try_write(s.as_bytes()); + Ok(()) + } +} + +/// Initialize and run the USB serial logger, never returns. +/// +/// Arguments specify the buffer size, log level and the USB driver, respectively. +/// +/// # Usage +/// +/// ``` +/// embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! run { + ( $x:expr, $l:expr, $p:ident ) => { + static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level($l)); + } + let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; + }; +} diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index b59ba8a22..ae3f3ac37 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -13,12 +13,36 @@ target = "thumbv7em-none-eabi" [features] defmt = ["dep:defmt", "embassy-usb-driver/defmt"] usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] +msos-descriptor = [] default = ["usbd-hid"] +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +max-interface-count-1 = [] +max-interface-count-2 = [] +max-interface-count-3 = [] +max-interface-count-4 = [] # Default +max-interface-count-5 = [] +max-interface-count-6 = [] +max-interface-count-7 = [] +max-interface-count-8 = [] + +max-handler-count-1 = [] +max-handler-count-2 = [] +max-handler-count-3 = [] +max-handler-count-4 = [] # Default +max-handler-count-5 = [] +max-handler-count-6 = [] +max-handler-count-7 = [] +max-handler-count-8 = [] + +# END AUTOGENERATED CONFIG FEATURES + [dependencies] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" } +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-usb/README.md b/embassy-usb/README.md new file mode 100644 index 000000000..a3d45b561 --- /dev/null +++ b/embassy-usb/README.md @@ -0,0 +1,44 @@ +# embassy-usb + +TODO crate description + +## Configuration + +`embassy-usb` has some configuration settings that are set at compile time, affecting sizes +and counts of buffers. + +They can be set in two ways: + +- Via Cargo features: enable a feature like `-`. `name` must be in lowercase and +use dashes instead of underscores. For example. `max-interface-count-3`. Only a selection of values +is available, check `Cargo.toml` for the list. +- Via environment variables at build time: set the variable named `EMBASSY_USB_`. For example +`EMBASSY_USB_MAX_INTERFACE_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. +Any value can be set, unlike with Cargo features. + +Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting +with different values, compilation fails. + +### `MAX_INTERFACE_COUNT` + +Max amount of interfaces that can be created in one device. Default: 4. + + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb/build.rs b/embassy-usb/build.rs new file mode 100644 index 000000000..33d32f7d3 --- /dev/null +++ b/embassy-usb/build.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("MAX_INTERFACE_COUNT", 4), + ("MAX_HANDLER_COUNT", 4), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + let crate_name = env::var("CARGO_PKG_NAME") + .unwrap() + .to_ascii_uppercase() + .replace('-', "_"); + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed={crate_name}_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + let prefix = format!("{crate_name}_"); + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&prefix) { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + if cfg.seen_feature { + panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); + } + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); +} diff --git a/embassy-usb/gen_config.py b/embassy-usb/gen_config.py new file mode 100644 index 000000000..67ce359cd --- /dev/null +++ b/embassy-usb/gen_config.py @@ -0,0 +1,74 @@ +import os + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +features = [] + + +def feature(name, default, min, max, pow2=None): + vals = set() + val = min + while val <= max: + vals.add(val) + if pow2 == True or (isinstance(pow2, int) and val >= pow2): + val *= 2 + else: + val += 1 + vals.add(default) + + features.append( + { + "name": name, + "default": default, + "vals": sorted(list(vals)), + } + ) + + +feature("max_interface_count", default=4, min=1, max=8) +feature("max_handler_count", default=4, min=1, max=8) + +# ========= Update Cargo.toml + +things = "" +for f in features: + name = f["name"].replace("_", "-") + for val in f["vals"]: + things += f"{name}-{val} = []" + if val == f["default"]: + things += " # Default" + things += "\n" + things += "\n" + +SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" +HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" +with open("Cargo.toml", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after +with open("Cargo.toml", "w") as f: + f.write(data) + + +# ========= Update build.rs + +things = "" +for f in features: + name = f["name"].upper() + things += f' ("{name}", {f["default"]}),\n' + +SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" +HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" +with open("build.rs", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + \ + things + " " + SEPARATOR_END + after +with open("build.rs", "w") as f: + f.write(data) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 87a8333bb..305dfa02e 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,10 +1,12 @@ use heapless::Vec; -use crate::control::ControlHandler; +use crate::config::*; use crate::descriptor::{BosWriter, DescriptorWriter}; use crate::driver::{Driver, Endpoint, EndpointType}; +#[cfg(feature = "msos-descriptor")] +use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; use crate::types::*; -use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; +use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -120,8 +122,8 @@ impl<'a> Config<'a> { /// [`UsbDevice`] builder. pub struct Builder<'d, D: Driver<'d>> { config: Config<'d>, - handler: Option<&'d dyn DeviceStateHandler>, - interfaces: Vec, MAX_INTERFACE_COUNT>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + interfaces: Vec, control_buf: &'d mut [u8], driver: D, @@ -130,6 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> { device_descriptor: DescriptorWriter<'d>, config_descriptor: DescriptorWriter<'d>, bos_descriptor: BosWriter<'d>, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter<'d>, } impl<'d, D: Driver<'d>> Builder<'d, D> { @@ -144,8 +149,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { device_descriptor_buf: &'d mut [u8], config_descriptor_buf: &'d mut [u8], bos_descriptor_buf: &'d mut [u8], + #[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8], control_buf: &'d mut [u8], - handler: Option<&'d dyn DeviceStateHandler>, ) -> Self { // Magic values specified in USB-IF ECN on IADs. if config.composite_with_iads @@ -173,32 +178,40 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { Builder { driver, - handler, config, interfaces: Vec::new(), + handlers: Vec::new(), control_buf, next_string_index: STRING_INDEX_CUSTOM_START, device_descriptor, config_descriptor, bos_descriptor, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf), } } /// Creates the [`UsbDevice`] instance with the configuration in this builder. pub fn build(mut self) -> UsbDevice<'d, D> { + #[cfg(feature = "msos-descriptor")] + let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor); + self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); UsbDevice::build( self.driver, self.config, - self.handler, + self.handlers, self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), self.interfaces, self.control_buf, + #[cfg(feature = "msos-descriptor")] + msos_descriptor, ) } @@ -215,14 +228,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { /// /// If it's not set, no IAD descriptor is added. pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { + let first_interface = InterfaceNumber::new(self.interfaces.len() as u8); let iface_count_index = if self.config.composite_with_iads { - self.config_descriptor.iad( - InterfaceNumber::new(self.interfaces.len() as _), - 0, - class, - subclass, - protocol, - ); + self.config_descriptor + .iad(first_interface, 0, class, subclass, protocol); Some(self.config_descriptor.position() - 5) } else { @@ -232,8 +241,52 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { FunctionBuilder { builder: self, iface_count_index, + + #[cfg(feature = "msos-descriptor")] + first_interface, } } + + /// Add a Handler. + /// + /// The Handler is called on some USB bus events, and to handle all control requests not already + /// handled by the USB stack. + pub fn handler(&mut self, handler: &'d mut dyn Handler) { + if self.handlers.push(handler).is_err() { + panic!( + "embassy-usb: handler list full. Increase the `max_handler_count` compile-time setting. Current value: {}", + MAX_HANDLER_COUNT + ) + } + } + + /// Allocates a new string index. + pub fn string(&mut self) -> StringIndex { + let index = self.next_string_index; + self.next_string_index += 1; + StringIndex::new(index) + } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Descriptor Set. + /// + /// Panics if called more than once. + pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) { + self.msos_descriptor.header(windows_version, vendor_code); + } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Device Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + self.msos_descriptor.device_feature(desc); + } + + #[cfg(feature = "msos-descriptor")] + /// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that + /// do not add their own. + pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> { + &mut self.msos_descriptor + } } /// Function builder. @@ -244,6 +297,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { builder: &'a mut Builder<'d, D>, iface_count_index: Option, + + #[cfg(feature = "msos-descriptor")] + first_interface: InterfaceNumber, +} + +impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> { + fn drop(&mut self) { + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.end_function(); + } } impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { @@ -257,14 +320,15 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { let number = self.builder.interfaces.len() as _; let iface = Interface { - handler: None, current_alt_setting: 0, num_alt_settings: 0, - num_strings: 0, }; if self.builder.interfaces.push(iface).is_err() { - panic!("max interface count reached") + panic!( + "embassy-usb: interface list full. Increase the `max_interface_count` compile-time setting. Current value: {}", + MAX_INTERFACE_COUNT + ) } InterfaceBuilder { @@ -273,6 +337,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { next_alt_setting_number: 0, } } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Function Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + if !self.builder.msos_descriptor.is_in_config_subset() { + self.builder.msos_descriptor.configuration(0); + } + + if !self.builder.msos_descriptor.is_in_function_subset() { + self.builder.msos_descriptor.function(self.first_interface); + } + + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.function_feature(desc); + } } /// Interface builder. @@ -288,17 +367,9 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { self.interface_number } - pub fn handler(&mut self, handler: &'d mut dyn ControlHandler) { - self.builder.interfaces[self.interface_number.0 as usize].handler = Some(handler); - } - /// Allocates a new string index. pub fn string(&mut self) -> StringIndex { - let index = self.builder.next_string_index; - self.builder.next_string_index += 1; - self.builder.interfaces[self.interface_number.0 as usize].num_strings += 1; - - StringIndex::new(index) + self.builder.string() } /// Add an alternate setting to the interface and write its descriptor. @@ -306,14 +377,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { /// Alternate setting numbers are guaranteed to be allocated consecutively, starting from 0. /// /// The first alternate setting, with number 0, is the default one. - pub fn alt_setting(&mut self, class: u8, subclass: u8, protocol: u8) -> InterfaceAltBuilder<'_, 'd, D> { + pub fn alt_setting( + &mut self, + class: u8, + subclass: u8, + protocol: u8, + interface_string: Option, + ) -> InterfaceAltBuilder<'_, 'd, D> { let number = self.next_alt_setting_number; self.next_alt_setting_number += 1; self.builder.interfaces[self.interface_number.0 as usize].num_alt_settings += 1; - self.builder - .config_descriptor - .interface_alt(self.interface_number, number, class, subclass, protocol, None); + self.builder.config_descriptor.interface_alt( + self.interface_number, + number, + class, + subclass, + protocol, + interface_string, + ); InterfaceAltBuilder { builder: self.builder, @@ -349,11 +431,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { self.builder.config_descriptor.write(descriptor_type, descriptor) } - fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointIn { + fn 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) + .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_in failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -361,11 +443,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { ep } - fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointOut { + fn 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) + .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_out failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -393,12 +475,25 @@ 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: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval) + 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) } /// Allocate a INTERRUPT OUT endpoint and write its descriptor. - pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval) + 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) + } + + /// 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) + } + + /// 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) } } diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index 09bb1cc8d..ff82ad40d 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -1,13 +1,15 @@ +//! CDC-ACM class implementation, aka Serial over USB. + use core::cell::Cell; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use embassy_sync::blocking_mutex::CriticalSectionMutex; -use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::types::*; -use crate::Builder; +use crate::{Builder, Handler}; /// This should be used as `device_class` when building the `UsbDevice`. pub const USB_CLASS_CDC: u8 = 0x02; @@ -18,7 +20,6 @@ const CDC_PROTOCOL_NONE: u8 = 0x00; const CS_INTERFACE: u8 = 0x24; const CDC_TYPE_HEADER: u8 = 0x00; -const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; const CDC_TYPE_ACM: u8 = 0x02; const CDC_TYPE_UNION: u8 = 0x06; @@ -29,12 +30,14 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; +/// Internal state for CDC-ACM pub struct State<'a> { control: MaybeUninit>, shared: ControlShared, } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { control: MaybeUninit::uninit(), @@ -64,6 +67,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { } struct Control<'a> { + comm_if: InterfaceNumber, shared: &'a ControlShared, } @@ -95,7 +99,7 @@ impl<'a> Control<'a> { } } -impl<'d> ControlHandler for Control<'d> { +impl<'d> Handler for Control<'d> { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -103,12 +107,18 @@ impl<'d> ControlHandler for Control<'d> { shared.rts.store(false, Ordering::Relaxed); } - fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_LINE_CODING if data.len() >= 7 => { let coding = LineCoding { @@ -120,7 +130,7 @@ impl<'d> ControlHandler for Control<'d> { self.shared().line_coding.lock(|x| x.set(coding)); debug!("Set line coding to: {:?}", coding); - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_CONTROL_LINE_STATE => { let dtr = (req.value & 0x0001) != 0; @@ -131,13 +141,19 @@ impl<'d> ControlHandler for Control<'d> { shared.rts.store(rts, Ordering::Relaxed); debug!("Set dtr {}, rts {}", dtr, rts); - OutResponse::Accepted + Some(OutResponse::Accepted) } - _ => OutResponse::Rejected, + _ => Some(OutResponse::Rejected), } } - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { @@ -148,9 +164,9 @@ impl<'d> ControlHandler for Control<'d> { buf[4] = coding.stop_bits as u8; buf[5] = coding.parity_type as u8; buf[6] = coding.data_bits; - InResponse::Accepted(&buf[0..7]) + Some(InResponse::Accepted(&buf[0..7])) } - _ => InResponse::Rejected, + _ => Some(InResponse::Rejected), } } } @@ -159,20 +175,15 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { - let control = state.control.write(Control { shared: &state.shared }); - - let control_shared = &state.shared; - assert!(builder.control_buf_len() >= 7); let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); // Control interface let mut iface = func.interface(); - iface.handler(control); let comm_if = iface.interface_number(); let data_if = u8::from(comm_if) + 1; - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None); alt.descriptor( CS_INTERFACE, @@ -186,7 +197,10 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { CS_INTERFACE, &[ CDC_TYPE_ACM, // bDescriptorSubtype - 0x00, // bmCapabilities + 0x02, // bmCapabilities: + // D1: Device supports the request combination of + // Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, + // and the Notification Serial_State. ], ); alt.descriptor( @@ -197,24 +211,26 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { data_if.into(), // bSubordinateInterface ], ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype - 0x00, // bmCapabilities - data_if.into(), // bDataInterface - ], - ); let comm_ep = alt.endpoint_interrupt_in(8, 255); // Data interface let mut iface = func.interface(); let data_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE, None); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); + drop(func); + + let control = state.control.write(Control { + shared: &state.shared, + comm_if, + }); + builder.handler(control); + + let control_shared = &state.shared; + CdcAcmClass { _comm_ep: comm_ep, _data_if: data_if, @@ -290,10 +306,15 @@ impl From for StopBits { #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ParityType { + /// No parity bit. None = 0, + /// Parity bit is 1 if the amount of `1` bits in the data byte is odd. Odd = 1, + /// Parity bit is 1 if the amount of `1` bits in the data byte is even. Even = 2, + /// Parity bit is always 1 Mark = 3, + /// Parity bit is always 0 Space = 4, } diff --git a/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy-usb/src/class/cdc_ncm/embassy_net.rs new file mode 100644 index 000000000..bc79b3671 --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -0,0 +1,100 @@ +//! [`embassy-net`](crates.io/crates/embassy-net) driver for the CDC-NCM class. +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_usb_driver::Driver; + +use super::{CdcNcmClass, Receiver, Sender}; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the CDC-NCM class. +/// +/// You must call `.run()` in a background task for the class to operate. +pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { + tx_usb: Sender<'d, D>, + rx_usb: Receiver<'d, D>, + ch: ch::Runner<'d, MTU>, +} + +impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { + /// Run the CDC-NCM class. + /// + /// You must call this in a background task for the class to operate. + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let rx_fut = async move { + loop { + trace!("WAITING for connection"); + state_chan.set_link_state(LinkState::Down); + + self.rx_usb.wait_connection().await.unwrap(); + + trace!("Connected"); + state_chan.set_link_state(LinkState::Up); + + loop { + let p = rx_chan.rx_buf().await; + match self.rx_usb.read_packet(p).await { + Ok(n) => rx_chan.rx_done(n), + Err(e) => { + warn!("error reading packet: {:?}", e); + break; + } + }; + } + } + }; + let tx_fut = async move { + loop { + let p = tx_chan.tx_buf().await; + if let Err(e) = self.tx_usb.write_packet(p).await { + warn!("Failed to TX packet: {:?}", e); + } + tx_chan.tx_done(); + } + }; + match select(rx_fut, tx_fut).await { + Either::First(x) => x, + Either::Second(x) => x, + } + } +} + +// would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug? +//pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd; +/// Type alias for the embassy-net driver for CDC-NCM. +pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>; + +impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Obtain a driver for using the CDC-NCM class with [`embassy-net`](crates.io/crates/embassy-net). + pub fn into_embassy_net_device( + self, + state: &'d mut State, + ethernet_address: [u8; 6], + ) -> (Runner<'d, D, MTU>, Device<'d, MTU>) { + let (tx_usb, rx_usb) = self.split(); + let (runner, device) = ch::new(&mut state.ch_state, ethernet_address); + + ( + Runner { + tx_usb, + rx_usb, + ch: runner, + }, + device, + ) + } +} diff --git a/embassy-usb/src/class/cdc_ncm.rs b/embassy-usb/src/class/cdc_ncm/mod.rs similarity index 82% rename from embassy-usb/src/class/cdc_ncm.rs rename to embassy-usb/src/class/cdc_ncm/mod.rs index a39b87e9b..262499ccb 100644 --- a/embassy-usb/src/class/cdc_ncm.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -1,10 +1,28 @@ +//! CDC-NCM class implementation, aka Ethernet over USB. +//! +//! # Compatibility +//! +//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11. +//! +//! Linux: Well-supported since forever. +//! +//! Android: Support for CDC-NCM is spotty and varies across manufacturers. +//! +//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12. +//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), +//! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. +//! This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 +//! and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 + use core::intrinsics::copy_nonoverlapping; use core::mem::{size_of, MaybeUninit}; -use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::types::*; -use crate::Builder; +use crate::{Builder, Handler}; + +pub mod embassy_net; /// This should be used as `device_class` when building the `UsbDevice`. pub const USB_CLASS_CDC: u8 = 0x02; @@ -97,17 +115,17 @@ fn byteify(buf: &mut [u8], data: T) -> &[u8] { &buf[..len] } +/// Internal state for the CDC-NCM class. pub struct State<'a> { - comm_control: MaybeUninit>, - data_control: MaybeUninit, + control: MaybeUninit>, shared: ControlShared, } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { - comm_control: MaybeUninit::uninit(), - data_control: MaybeUninit::uninit(), + control: MaybeUninit::uninit(), shared: Default::default(), } } @@ -124,29 +142,55 @@ impl Default for ControlShared { } } -struct CommControl<'a> { +struct Control<'a> { mac_addr_string: StringIndex, shared: &'a ControlShared, mac_addr_str: [u8; 12], + comm_if: InterfaceNumber, + data_if: InterfaceNumber, } -impl<'d> ControlHandler for CommControl<'d> { - fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse { +impl<'d> Handler for Control<'d> { + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + if iface != self.data_if { + return; + } + + match alternate_setting { + ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), + ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), + _ => unreachable!(), + } + } + + fn control_out(&mut self, req: control::Request, _data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_NTB_INPUT_SIZE => { // TODO - OutResponse::Accepted + Some(OutResponse::Accepted) } - _ => OutResponse::Rejected, + _ => Some(OutResponse::Rejected), } } - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_GET_NTB_PARAMETERS => { let res = NtbParameters { @@ -167,9 +211,9 @@ impl<'d> ControlHandler for CommControl<'d> { max_datagram_count: 1, // We only decode 1 packet per NTB }, }; - InResponse::Accepted(byteify(buf, res)) + Some(InResponse::Accepted(byteify(buf, res))) } - _ => InResponse::Rejected, + _ => Some(InResponse::Rejected), } } @@ -194,18 +238,7 @@ impl<'d> ControlHandler for CommControl<'d> { } } -struct DataControl {} - -impl ControlHandler for DataControl { - fn set_alternate_setting(&mut self, alternate_setting: u8) { - match alternate_setting { - ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), - ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), - _ => unreachable!(), - } - } -} - +/// CDC-NCM class pub struct CdcNcmClass<'d, D: Driver<'d>> { _comm_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -218,6 +251,7 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Create a new CDC NCM class. pub fn new( builder: &mut Builder<'d, D>, state: &'d mut State<'d>, @@ -231,13 +265,8 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { // Control interface let mut iface = func.interface(); let mac_addr_string = iface.string(); - iface.handler(state.comm_control.write(CommControl { - mac_addr_string, - shared: &state.shared, - mac_addr_str: [0; 12], - })); let comm_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None); alt.descriptor( CS_INTERFACE, @@ -285,13 +314,23 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { // Data interface let mut iface = func.interface(); - iface.handler(state.data_control.write(DataControl {})); let data_if = iface.interface_number(); - let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); + let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); + drop(func); + + let control = state.control.write(Control { + mac_addr_string, + shared: &state.shared, + mac_addr_str: [0; 12], + comm_if, + data_if, + }); + builder.handler(control); + CdcNcmClass { _comm_if: comm_if, comm_ep, @@ -302,6 +341,9 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { ( Sender { @@ -317,12 +359,18 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } +/// CDC NCM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcNcmClass::split`] pub struct Sender<'d, D: Driver<'d>> { write_ep: D::EndpointIn, seq: u16, } impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Write a packet. + /// + /// This waits until the packet is succesfully stored in the CDC-NCM endpoint buffers. pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { let seq = self.seq; self.seq = self.seq.wrapping_add(1); @@ -376,6 +424,9 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { } } +/// CDC NCM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcNcmClass::split`] pub struct Receiver<'d, D: Driver<'d>> { data_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -383,7 +434,9 @@ pub struct Receiver<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> Receiver<'d, D> { - /// Reads a single packet from the OUT endpoint. + /// Write a network packet. + /// + /// This waits until a packet is succesfully received from the endpoint buffers. pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { // Retry loop loop { diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs index 4d1fa995f..974268c62 100644 --- a/embassy-usb/src/class/hid.rs +++ b/embassy-usb/src/class/hid.rs @@ -1,3 +1,5 @@ +//! USB HID (Human Interface Device) class implementation. + use core::mem::MaybeUninit; use core::ops::Range; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -7,9 +9,10 @@ use ssmarshal::serialize; #[cfg(feature = "usbd-hid")] use usbd_hid::descriptor::AsInputReport; -use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; +use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use crate::Builder; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; const USB_CLASS_HID: u8 = 0x03; const USB_SUBCLASS_NONE: u8 = 0x00; @@ -28,6 +31,7 @@ const HID_REQ_SET_REPORT: u8 = 0x09; const HID_REQ_GET_PROTOCOL: u8 = 0x03; const HID_REQ_SET_PROTOCOL: u8 = 0x0b; +/// Configuration for the HID class. pub struct Config<'d> { /// HID report descriptor. pub report_descriptor: &'d [u8], @@ -46,11 +50,15 @@ pub struct Config<'d> { pub max_packet_size: u16, } +/// Report ID #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReportId { + /// IN report In(u8), + /// OUT report Out(u8), + /// Feature report Feature(u8), } @@ -65,12 +73,14 @@ impl ReportId { } } +/// Internal state for USB HID. pub struct State<'d> { control: MaybeUninit>, out_report_offset: AtomicUsize, } impl<'d> State<'d> { + /// Create a new `State`. pub fn new() -> Self { State { control: MaybeUninit::uninit(), @@ -79,6 +89,7 @@ impl<'d> State<'d> { } } +/// USB HID reader/writer. pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { reader: HidReader<'d, D, READ_N>, writer: HidWriter<'d, D, WRITE_N>, @@ -90,18 +101,12 @@ fn build<'d, D: Driver<'d>>( config: Config<'d>, with_out_endpoint: bool, ) -> (Option, D::EndpointIn, &'d AtomicUsize) { - let control = state.control.write(Control::new( - config.report_descriptor, - config.request_handler, - &state.out_report_offset, - )); - let len = config.report_descriptor.len(); let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); let mut iface = func.interface(); - iface.handler(control); - let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + let if_num = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); // HID descriptor alt.descriptor( @@ -129,6 +134,16 @@ fn build<'d, D: Driver<'d>>( None }; + drop(func); + + let control = state.control.write(Control::new( + if_num, + config.report_descriptor, + config.request_handler, + &state.out_report_offset, + )); + builder.handler(control); + (ep_out, ep_in, &state.out_report_offset) } @@ -180,20 +195,30 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit } } +/// USB HID writer. +/// +/// You can obtain a `HidWriter` using [`HidReaderWriter::split`]. pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { ep_in: D::EndpointIn, } +/// USB HID reader. +/// +/// You can obtain a `HidReader` using [`HidReaderWriter::split`]. pub struct HidReader<'d, D: Driver<'d>, const N: usize> { ep_out: D::EndpointOut, offset: &'d AtomicUsize, } +/// Error when reading a HID report. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReadError { + /// The given buffer was too small to read the received report. BufferOverflow, + /// The endpoint is disabled. Disabled, + /// The report was only partially read. See [`HidReader::read`] for details. Sync(Range), } @@ -299,7 +324,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { /// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output /// reports may be split across multiple packets) and this method's future /// is dropped after some packets have been read, the next call to `read()` - /// will return a [`ReadError::SyncError()`]. The range in the sync error + /// will return a [`ReadError::Sync`]. The range in the sync error /// indicates the portion `buf` that was filled by the current call to /// `read()`. If the dropped future used the same `buf`, then `buf` will /// contain the full report. @@ -344,6 +369,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { } } +/// Handler for HID-related control requests. pub trait RequestHandler { /// Reads the value of report `id` into `buf` returning the size. /// @@ -379,6 +405,7 @@ pub trait RequestHandler { } struct Control<'d> { + if_num: InterfaceNumber, report_descriptor: &'d [u8], request_handler: Option<&'d dyn RequestHandler>, out_report_offset: &'d AtomicUsize, @@ -387,11 +414,13 @@ struct Control<'d> { impl<'d> Control<'d> { fn new( + if_num: InterfaceNumber, report_descriptor: &'d [u8], request_handler: Option<&'d dyn RequestHandler>, out_report_offset: &'d AtomicUsize, ) -> Self { Control { + if_num, report_descriptor, request_handler, out_report_offset, @@ -417,88 +446,100 @@ impl<'d> Control<'d> { } } -impl<'d> ControlHandler for Control<'d> { +impl<'d> Handler for Control<'d> { fn reset(&mut self) { self.out_report_offset.store(0, Ordering::Release); } - fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> { - match (req.value >> 8) as u8 { - HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), - HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), - _ => InResponse::Rejected, + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.if_num.0 as u16) + { + return None; } - } - fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { trace!("HID control_out {:?} {=[u8]:x}", req, data); - if let RequestType::Class = req.request_type { - match req.request { - HID_REQ_SET_IDLE => { - if let Some(handler) = self.request_handler { - let id = req.value as u8; - let id = (id != 0).then(|| ReportId::In(id)); - let dur = u32::from(req.value >> 8); - let dur = if dur == 0 { u32::MAX } else { 4 * dur }; - handler.set_idle_ms(id, dur); - } - OutResponse::Accepted - } - HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { - (Ok(id), Some(handler)) => handler.set_report(id, data), - _ => OutResponse::Rejected, - }, - HID_REQ_SET_PROTOCOL => { - if req.value == 1 { - OutResponse::Accepted - } else { - warn!("HID Boot Protocol is unsupported."); - OutResponse::Rejected // UNSUPPORTED: Boot Protocol - } - } - _ => OutResponse::Rejected, - } - } else { - OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR - } - } - - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - trace!("HID control_in {:?}", req); match req.request { - HID_REQ_GET_REPORT => { - let size = match ReportId::try_from(req.value) { - Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), - Err(_) => None, - }; - - if let Some(size) = size { - InResponse::Accepted(&buf[0..size]) - } else { - InResponse::Rejected - } - } - HID_REQ_GET_IDLE => { + HID_REQ_SET_IDLE => { if let Some(handler) = self.request_handler { let id = req.value as u8; let id = (id != 0).then(|| ReportId::In(id)); - if let Some(dur) = handler.get_idle_ms(id) { - let dur = u8::try_from(dur / 4).unwrap_or(0); - buf[0] = dur; - InResponse::Accepted(&buf[0..1]) - } else { - InResponse::Rejected - } + let dur = u32::from(req.value >> 8); + let dur = if dur == 0 { u32::MAX } else { 4 * dur }; + handler.set_idle_ms(id, dur); + } + Some(OutResponse::Accepted) + } + HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { + (Ok(id), Some(handler)) => Some(handler.set_report(id, data)), + _ => Some(OutResponse::Rejected), + }, + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + Some(OutResponse::Accepted) } else { - InResponse::Rejected + warn!("HID Boot Protocol is unsupported."); + Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol } } - HID_REQ_GET_PROTOCOL => { - // UNSUPPORTED: Boot Protocol - buf[0] = 1; - InResponse::Accepted(&buf[0..1]) + _ => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if req.index != self.if_num.0 as u16 { + return None; + } + + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Interface) => match req.request { + Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => Some(InResponse::Accepted(self.report_descriptor)), + HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)), + _ => Some(InResponse::Rejected), + }, + + _ => Some(InResponse::Rejected), + }, + (RequestType::Class, Recipient::Interface) => { + trace!("HID control_in {:?}", req); + match req.request { + HID_REQ_GET_REPORT => { + let size = match ReportId::try_from(req.value) { + Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + Some(InResponse::Accepted(&buf[0..size])) + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_IDLE => { + if let Some(handler) = self.request_handler { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + if let Some(dur) = handler.get_idle_ms(id) { + let dur = u8::try_from(dur / 4).unwrap_or(0); + buf[0] = dur; + Some(InResponse::Accepted(&buf[0..1])) + } else { + Some(InResponse::Rejected) + } + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_PROTOCOL => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + Some(InResponse::Accepted(&buf[0..1])) + } + _ => Some(InResponse::Rejected), + } } - _ => InResponse::Rejected, + _ => None, } } } diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index af27577a6..b23e03d40 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs @@ -1,3 +1,4 @@ +//! Implementations of well-known USB classes. pub mod cdc_acm; pub mod cdc_ncm; pub mod hid; diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index d6d0c6565..ceccfd85b 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -2,7 +2,6 @@ use core::mem; use crate::driver::Direction; -use crate::types::StringIndex; /// Control request type. #[repr(u8)] @@ -126,72 +125,22 @@ impl Request { } } +/// Response for a CONTROL OUT request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OutResponse { + /// The request was accepted. Accepted, + /// The request was rejected. Rejected, } +/// Response for a CONTROL IN request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InResponse<'a> { + /// The request was accepted. The buffer contains the response data. Accepted(&'a [u8]), + /// The request was rejected. Rejected, } - -/// Handler for control requests. -/// -/// All methods are optional callbacks that will be called by -/// [`UsbDevice::run()`](crate::UsbDevice::run) -pub trait ControlHandler { - /// Called after a USB reset after the bus reset sequence is complete. - fn reset(&mut self) {} - - fn set_alternate_setting(&mut self, alternate_setting: u8) { - let _ = alternate_setting; - } - - /// Called when a control request is received with direction HostToDevice. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - /// * `data` - The data from the request. - fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { - let _ = (req, data); - OutResponse::Rejected - } - - /// Called when a control request is received with direction DeviceToHost. - /// - /// You should write the response somewhere (usually to `buf`, but you may use another buffer - /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - let _ = (req, buf); - InResponse::Rejected - } - - /// Called when a GET DESCRIPTOR control request is received on the interface. - /// - /// You should write the response somewhere (usually to `buf`, but you may use another buffer - /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - fn get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - let _ = (req, buf); - InResponse::Rejected - } - - /// Called when a GET_DESCRIPTOR STRING control request is received. - fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> { - let _ = (index, lang_id); - None - } -} diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index 497f03196..ae38e26ca 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,3 +1,5 @@ +//! Utilities for writing USB descriptors. + use crate::builder::Config; use crate::driver::EndpointInfo; use crate::types::*; @@ -236,7 +238,7 @@ impl<'a> DescriptorWriter<'a> { endpoint.ep_type as u8, // bmAttributes endpoint.max_packet_size as u8, (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize - endpoint.interval, // bInterval + endpoint.interval_ms, // bInterval ], ); } diff --git a/embassy-usb/src/descriptor_reader.rs b/embassy-usb/src/descriptor_reader.rs index d64bcb73b..05adcce60 100644 --- a/embassy-usb/src/descriptor_reader.rs +++ b/embassy-usb/src/descriptor_reader.rs @@ -1,5 +1,6 @@ use crate::descriptor::descriptor_type; use crate::driver::EndpointAddress; +use crate::types::InterfaceNumber; #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -75,7 +76,7 @@ impl<'a, 'b> Iterator for DescriptorIter<'a, 'b> { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointInfo { pub configuration: u8, - pub interface: u8, + pub interface: InterfaceNumber, pub interface_alt: u8, pub ep_address: EndpointAddress, } @@ -83,7 +84,7 @@ pub struct EndpointInfo { pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result<(), ReadError> { let mut ep = EndpointInfo { configuration: 0, - interface: 0, + interface: InterfaceNumber(0), interface_alt: 0, ep_address: EndpointAddress::from(0), }; @@ -96,7 +97,7 @@ pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result< ep.configuration = r.read_u8()?; } descriptor_type::INTERFACE => { - ep.interface = r.read_u8()?; + ep.interface = InterfaceNumber(r.read_u8()?); ep.interface_alt = r.read_u8()?; } descriptor_type::ENDPOINT => { diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 661b84119..bfeccd5fe 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] #![feature(type_alias_impl_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; @@ -11,12 +13,19 @@ pub mod class; pub mod control; pub mod descriptor; mod descriptor_reader; +pub mod msos; pub mod types; +mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + use embassy_futures::select::{select, Either}; use heapless::Vec; pub use crate::builder::{Builder, Config}; +use crate::config::*; use crate::control::*; use crate::descriptor::*; use crate::descriptor_reader::foreach_endpoint; @@ -46,10 +55,13 @@ pub enum UsbDeviceState { Configured, } +/// Error returned by [`UsbDevice::remote_wakeup`]. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RemoteWakeupError { + /// The USB device is not suspended, or remote wakeup was not enabled. InvalidState, + /// The underlying driver doesn't support remote wakeup. Unsupported, } @@ -65,41 +77,95 @@ pub const CONFIGURATION_NONE: u8 = 0; /// The bConfiguration value for the single configuration supported by this device. pub const CONFIGURATION_VALUE: u8 = 1; -pub const MAX_INTERFACE_COUNT: usize = 4; - const STRING_INDEX_MANUFACTURER: u8 = 1; const STRING_INDEX_PRODUCT: u8 = 2; const STRING_INDEX_SERIAL_NUMBER: u8 = 3; const STRING_INDEX_CUSTOM_START: u8 = 4; -/// A handler trait for changes in the device state of the [UsbDevice]. -pub trait DeviceStateHandler { +/// Handler for device events and control requests. +/// +/// All methods are optional callbacks that will be called by +/// [`UsbDevice::run()`](crate::UsbDevice::run) +pub trait Handler { /// Called when the USB device has been enabled or disabled. - fn enabled(&self, _enabled: bool) {} + fn enabled(&mut self, _enabled: bool) {} - /// Called when the host resets the device. - fn reset(&self) {} + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) {} /// Called when the host has set the address of the device to `addr`. - fn addressed(&self, _addr: u8) {} + fn addressed(&mut self, _addr: u8) {} /// Called when the host has enabled or disabled the configuration of the device. - fn configured(&self, _configured: bool) {} + fn configured(&mut self, _configured: bool) {} /// Called when the bus has entered or exited the suspend state. - fn suspended(&self, _suspended: bool) {} + fn suspended(&mut self, _suspended: bool) {} /// Called when remote wakeup feature is enabled or disabled. - fn remote_wakeup_enabled(&self, _enabled: bool) {} + fn remote_wakeup_enabled(&mut self, _enabled: bool) {} + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + let _ = iface; + let _ = alternate_setting; + } + + /// Called when a control request is received with direction HostToDevice. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `data` - The data from the request. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + let _ = (req, data); + None + } + + /// Called when a control request is received with direction DeviceToHost. + /// + /// You should write the response somewhere (usually to `buf`, but you may use another buffer + /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + let _ = (req, buf); + None + } + + /// Called when a GET_DESCRIPTOR STRING control request is received. + fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> { + let _ = (index, lang_id); + None + } } -struct Interface<'d> { - handler: Option<&'d mut dyn ControlHandler>, +struct Interface { current_alt_setting: u8, num_alt_settings: u8, - num_strings: u8, } +/// Main struct for the USB device stack. pub struct UsbDevice<'d, D: Driver<'d>> { control_buf: &'d mut [u8], control: D::ControlPipe, @@ -108,7 +174,6 @@ pub struct UsbDevice<'d, D: Driver<'d>> { struct Inner<'d, D: Driver<'d>> { bus: D::Bus, - handler: Option<&'d dyn DeviceStateHandler>, config: Config<'d>, device_descriptor: &'d [u8], @@ -122,25 +187,29 @@ struct Inner<'d, D: Driver<'d>> { /// Our device address, or 0 if none. address: u8, - /// When receiving a set addr control request, we have to apply it AFTER we've - /// finished handling the control request, as the status stage still has to be - /// handled with addr 0. - /// If true, do a set_addr after finishing the current control req. + /// SET_ADDRESS requests have special handling depending on the driver. + /// This flag indicates that requests must be handled by `ControlPipe::accept_set_address()` + /// instead of regular `accept()`. set_address_pending: bool, - interfaces: Vec, MAX_INTERFACE_COUNT>, + interfaces: Vec, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { pub(crate) fn build( driver: D, config: Config<'d>, - handler: Option<&'d dyn DeviceStateHandler>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], - interfaces: Vec, MAX_INTERFACE_COUNT>, + interfaces: Vec, control_buf: &'d mut [u8], + #[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, ) -> UsbDevice<'d, D> { // Start the USB bus. // This prevent further allocation by consuming the driver. @@ -152,7 +221,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { inner: Inner { bus, config, - handler, device_descriptor, config_descriptor, bos_descriptor, @@ -164,6 +232,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { address: 0, set_address_pending: false, interfaces, + handlers, + #[cfg(feature = "msos-descriptor")] + msos_descriptor, }, } } @@ -206,7 +277,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.inner.suspended = false; self.inner.remote_wakeup_enabled = false; - if let Some(h) = &self.inner.handler { + for h in &mut self.inner.handlers { h.enabled(false); } } @@ -235,7 +306,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.inner.bus.remote_wakeup().await?; self.inner.suspended = false; - if let Some(h) = &self.inner.handler { + for h in &mut self.inner.handlers { h.suspended(false); } @@ -254,11 +325,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { Direction::In => self.handle_control_in(req).await, Direction::Out => self.handle_control_out(req).await, } - - if self.inner.set_address_pending { - self.inner.bus.set_address(self.inner.address); - self.inner.set_address_pending = false; - } } async fn handle_control_in(&mut self, req: Request) { @@ -328,7 +394,14 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { trace!(" control out data: {:02x?}", data); match self.inner.handle_control_out(req, data) { - OutResponse::Accepted => self.control.accept().await, + OutResponse::Accepted => { + if self.inner.set_address_pending { + self.control.accept_set_address(self.inner.address).await; + self.inner.set_address_pending = false; + } else { + self.control.accept().await + } + } OutResponse::Rejected => self.control.reject().await, } } @@ -344,29 +417,29 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.remote_wakeup_enabled = false; self.address = 0; - for iface in self.interfaces.iter_mut() { - iface.current_alt_setting = 0; - if let Some(h) = &mut iface.handler { - h.reset(); - h.set_alternate_setting(0); - } + for h in &mut self.handlers { + h.reset(); } - if let Some(h) = &self.handler { - h.reset(); + for (i, iface) in self.interfaces.iter_mut().enumerate() { + iface.current_alt_setting = 0; + + for h in &mut self.handlers { + h.set_alternate_setting(InterfaceNumber::new(i as _), 0); + } } } Event::Resume => { trace!("usb: resume"); self.suspended = false; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.suspended(false); } } Event::Suspend => { trace!("usb: suspend"); self.suspended = true; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.suspended(true); } } @@ -375,7 +448,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.bus.enable().await; self.device_state = UsbDeviceState::Default; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.enabled(true); } } @@ -384,7 +457,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.bus.disable().await; self.device_state = UsbDeviceState::Unpowered; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.enabled(false); } } @@ -399,14 +472,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = false; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.remote_wakeup_enabled(false); } OutResponse::Accepted } (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = true; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.remote_wakeup_enabled(true); } OutResponse::Accepted @@ -415,7 +488,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.address = addr as u8; self.set_address_pending = true; self.device_state = UsbDeviceState::Addressed; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.addressed(self.address); } OutResponse::Accepted @@ -426,14 +499,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { // Enable all endpoints of selected alt settings. foreach_endpoint(self.config_descriptor, |ep| { - let iface = &self.interfaces[ep.interface as usize]; + let iface = &self.interfaces[ep.interface.0 as usize]; self.bus .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); }) .unwrap(); - // Notify handler. - if let Some(h) = &self.handler { + // Notify handlers. + for h in &mut self.handlers { h.configured(true); } @@ -451,8 +524,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { }) .unwrap(); - // Notify handler. - if let Some(h) = &self.handler { + // Notify handlers. + for h in &mut self.handlers { h.configured(false); } @@ -462,7 +535,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { _ => OutResponse::Rejected, }, (RequestType::Standard, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { + let iface_num = InterfaceNumber::new(req.index as _); + let iface = match self.interfaces.get_mut(iface_num.0 as usize) { Some(iface) => iface, None => return OutResponse::Rejected, }; @@ -480,7 +554,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { // Enable/disable EPs of this interface as needed. foreach_endpoint(self.config_descriptor, |ep| { - if ep.interface == req.index as u8 { + if ep.interface == iface_num { self.bus .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); } @@ -488,10 +562,9 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { .unwrap(); // TODO check it is valid (not out of range) - // TODO actually enable/disable endpoints. - if let Some(handler) = &mut iface.handler { - handler.set_alternate_setting(new_altsetting); + for h in &mut self.handlers { + h.set_alternate_setting(iface_num, new_altsetting); } OutResponse::Accepted } @@ -511,17 +584,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } _ => OutResponse::Rejected, }, - (RequestType::Class, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { - Some(iface) => iface, - None => return OutResponse::Rejected, - }; - match &mut iface.handler { - Some(handler) => handler.control_out(req, data), - None => OutResponse::Rejected, - } - } - _ => OutResponse::Rejected, + _ => self.handle_control_out_delegated(req, data), } } @@ -566,11 +629,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { buf[0] = iface.current_alt_setting; InResponse::Accepted(&buf[..1]) } - Request::GET_DESCRIPTOR => match &mut iface.handler { - Some(handler) => handler.get_descriptor(req, buf), - None => InResponse::Rejected, - }, - _ => InResponse::Rejected, + _ => self.handle_control_in_delegated(req, buf), } } (RequestType::Standard, Recipient::Endpoint) => match req.request { @@ -585,21 +644,48 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } _ => InResponse::Rejected, }, - (RequestType::Class, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { - Some(iface) => iface, - None => return InResponse::Rejected, - }; - - match &mut iface.handler { - Some(handler) => handler.control_in(req, buf), - None => InResponse::Rejected, + #[cfg(feature = "msos-descriptor")] + (RequestType::Vendor, Recipient::Device) => { + if !self.msos_descriptor.is_empty() + && req.request == self.msos_descriptor.vendor_code() + && req.index == 7 + { + // Index 7 retrieves the MS OS Descriptor Set + InResponse::Accepted(self.msos_descriptor.descriptor()) + } else { + self.handle_control_in_delegated(req, buf) } } - _ => InResponse::Rejected, + _ => self.handle_control_in_delegated(req, buf), } } + fn handle_control_out_delegated(&mut self, req: Request, data: &[u8]) -> OutResponse { + for h in &mut self.handlers { + if let Some(res) = h.control_out(req, data) { + return res; + } + } + OutResponse::Rejected + } + + fn handle_control_in_delegated<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + unsafe fn extend_lifetime<'x, 'y>(r: InResponse<'x>) -> InResponse<'y> { + core::mem::transmute(r) + } + + for h in &mut self.handlers { + if let Some(res) = h.control_in(req, buf) { + // safety: the borrow checker isn't smart enough to know this pattern (returning a + // borrowed value from inside the loop) is sound. Workaround by unsafely extending lifetime. + // Also, Polonius (the WIP new borrow checker) does accept it. + + return unsafe { extend_lifetime(res) }; + } + } + InResponse::Rejected + } + fn handle_get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { let (dtype, index) = req.descriptor_type_index(); @@ -620,30 +706,16 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { STRING_INDEX_PRODUCT => self.config.product, STRING_INDEX_SERIAL_NUMBER => self.config.serial_number, _ => { - // Find out which iface owns this string index. - let mut index_left = index - STRING_INDEX_CUSTOM_START; - let mut the_iface = None; - for iface in &mut self.interfaces { - if index_left < iface.num_strings { - the_iface = Some(iface); + let mut s = None; + for handler in &mut self.handlers { + let index = StringIndex::new(index); + let lang_id = req.index; + if let Some(res) = handler.get_string(index, lang_id) { + s = Some(res); break; } - index_left -= iface.num_strings; - } - - if let Some(iface) = the_iface { - if let Some(handler) = &mut iface.handler { - let index = StringIndex::new(index); - let lang_id = req.index; - handler.get_string(index, lang_id) - } else { - warn!("String requested to an interface with no handler."); - None - } - } else { - warn!("String requested but didn't match to an interface."); - None } + s } }; @@ -655,7 +727,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { buf[1] = descriptor_type::STRING; let mut pos = 2; for c in s.encode_utf16() { - if pos >= buf.len() { + if pos + 2 >= buf.len() { panic!("control buffer too small"); } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs new file mode 100644 index 000000000..b1e0335ee --- /dev/null +++ b/embassy-usb/src/msos.rs @@ -0,0 +1,727 @@ +#![cfg(feature = "msos-descriptor")] + +//! Microsoft OS Descriptors +//! +//! + +use core::mem::size_of; + +use super::{capability_type, BosWriter}; +use crate::types::InterfaceNumber; + +/// A serialized Microsoft OS 2.0 Descriptor set. +/// +/// Create with [`DeviceDescriptorSetBuilder`]. +pub struct MsOsDescriptorSet<'d> { + descriptor: &'d [u8], + vendor_code: u8, +} + +impl<'d> MsOsDescriptorSet<'d> { + /// Gets the raw bytes of the MS OS descriptor + pub fn descriptor(&self) -> &[u8] { + self.descriptor + } + + /// Gets the vendor code used by the host to retrieve the MS OS descriptor + pub fn vendor_code(&self) -> u8 { + self.vendor_code + } + + /// Returns `true` if no MS OS descriptor data is available + pub fn is_empty(&self) -> bool { + self.descriptor.is_empty() + } +} + +/// Writes a Microsoft OS 2.0 Descriptor set into a buffer. +pub struct MsOsDescriptorWriter<'d> { + buf: &'d mut [u8], + + position: usize, + config_mark: Option, + function_mark: Option, + vendor_code: u8, +} + +impl<'d> MsOsDescriptorWriter<'d> { + pub(crate) fn new(buf: &'d mut [u8]) -> Self { + MsOsDescriptorWriter { + buf, + position: 0, + config_mark: None, + function_mark: None, + vendor_code: 0, + } + } + + pub(crate) fn build(mut self, bos: &mut BosWriter) -> MsOsDescriptorSet<'d> { + self.end(); + + if self.is_empty() { + MsOsDescriptorSet { + descriptor: &[], + vendor_code: 0, + } + } else { + self.write_bos(bos); + MsOsDescriptorSet { + descriptor: &self.buf[..self.position], + vendor_code: self.vendor_code, + } + } + } + + /// Returns `true` if the MS OS descriptor header has not yet been written + pub fn is_empty(&self) -> bool { + self.position == 0 + } + + /// Returns `true` if a configuration subset header has been started + pub fn is_in_config_subset(&self) -> bool { + self.config_mark.is_some() + } + + /// Returns `true` if a function subset header has been started and not yet ended + pub fn is_in_function_subset(&self) -> bool { + self.function_mark.is_some() + } + + /// Write the MS OS descriptor set header. + /// + /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] + /// module. + /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. + pub fn header(&mut self, windows_version: u32, vendor_code: u8) { + assert!(self.is_empty(), "You can only call MsOsDescriptorWriter::header once"); + self.write(DescriptorSetHeader::new(windows_version)); + self.vendor_code = vendor_code; + } + + /// Add a device level feature descriptor. + /// + /// Note that some feature descriptors may only be used at the device level in non-composite devices. + /// Those features must be written before the first call to [`Self::configuration`]. + pub fn device_feature(&mut self, desc: T) { + assert!( + !self.is_empty(), + "device features may only be added after the header is written" + ); + assert!( + self.config_mark.is_none(), + "device features must be added before the first configuration subset" + ); + self.write(desc); + } + + /// Add a configuration subset. + pub fn configuration(&mut self, config: u8) { + assert!( + !self.is_empty(), + "MsOsDescriptorWriter: configuration must be called after header" + ); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + self.config_mark = Some(self.position); + self.write(ConfigurationSubsetHeader::new(config)); + } + + /// Add a function subset. + pub fn function(&mut self, first_interface: InterfaceNumber) { + assert!( + self.config_mark.is_some(), + "MsOsDescriptorWriter: function subset requires a configuration subset" + ); + self.end_function(); + self.function_mark = Some(self.position); + self.write(FunctionSubsetHeader::new(first_interface)); + } + + /// Add a function level feature descriptor. + /// + /// Note that some features may only be used at the function level. Those features must be written after a call + /// to [`Self::function`]. + pub fn function_feature(&mut self, desc: T) { + assert!( + self.function_mark.is_some(), + "function features may only be added to a function subset" + ); + self.write(desc); + } + + /// Ends the current function subset (if any) + pub fn end_function(&mut self) { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + } + + fn write(&mut self, desc: T) { + desc.write_to(&mut self.buf[self.position..]); + self.position += desc.size(); + } + + fn end_subset(buf: &mut [u8], position: usize, mark: &mut Option) { + if let Some(mark) = mark.take() { + let len = position - mark; + let p = mark + T::LENGTH_OFFSET; + buf[p..(p + 2)].copy_from_slice(&(len as u16).to_le_bytes()); + } + } + + fn end(&mut self) { + if self.position > 0 { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + Self::end_subset::(self.buf, self.position, &mut Some(0)); + } + } + + fn write_bos(&mut self, bos: &mut BosWriter) { + let windows_version = &self.buf[4..8]; + let len = (self.position as u16).to_le_bytes(); + bos.capability( + capability_type::PLATFORM, + &[ + 0, // reserved + // platform capability UUID, Microsoft OS 2.0 platform compabitility + 0xdf, + 0x60, + 0xdd, + 0xd8, + 0x89, + 0x45, + 0xc7, + 0x4c, + 0x9c, + 0xd2, + 0x65, + 0x9d, + 0x9e, + 0x64, + 0x8a, + 0x9f, + // Minimum compatible Windows version + windows_version[0], + windows_version[1], + windows_version[2], + windows_version[3], + // Descriptor set length + len[0], + len[1], + self.vendor_code, + 0x0, // Device does not support alternate enumeration + ], + ); + } +} + +/// Microsoft Windows version codes +/// +/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors. +pub mod windows_version { + /// Windows 8.1 (aka `NTDDI_WINBLUE`) + pub const WIN8_1: u32 = 0x06030000; + /// Windows 10 + pub const WIN10: u32 = 0x0A000000; +} + +mod sealed { + use core::mem::size_of; + + /// A trait for descriptors + pub trait Descriptor: Sized { + const TYPE: super::DescriptorType; + + /// The size of the descriptor's header. + fn size(&self) -> usize { + size_of::() + } + + fn write_to(&self, buf: &mut [u8]); + } + + pub trait DescriptorSet: Descriptor { + const LENGTH_OFFSET: usize; + } +} + +use sealed::*; + +/// Copies the data of `t` into `buf`. +/// +/// # Safety +/// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) +unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { + let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::()); + assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full"); + (&mut buf[..bytes.len()]).copy_from_slice(bytes); +} + +/// Table 9. Microsoft OS 2.0 descriptor wDescriptorType values. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DescriptorType { + /// MS OS descriptor set header + SetHeaderDescriptor = 0, + /// Configuration subset header + SubsetHeaderConfiguration = 1, + /// Function subset header + SubsetHeaderFunction = 2, + /// Compatible device ID feature descriptor + FeatureCompatibleId = 3, + /// Registry property feature descriptor + FeatureRegProperty = 4, + /// Minimum USB resume time feature descriptor + FeatureMinResumeTime = 5, + /// Vendor revision feature descriptor + FeatureModelId = 6, + /// CCGP device descriptor feature descriptor + FeatureCcgpDevice = 7, + /// Vendor revision feature descriptor + FeatureVendorRevision = 8, +} + +/// Table 5. Descriptor set information structure. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct DescriptorSetInformation { + dwWindowsVersion: u32, + wMSOSDescriptorSetTotalLength: u16, + bMS_VendorCode: u8, + bAltEnumCode: u8, +} + +/// Table 4. Microsoft OS 2.0 platform capability descriptor header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct PlatformDescriptor { + bLength: u8, + bDescriptorType: u8, + bDevCapabilityType: u8, + bReserved: u8, + platformCapabilityUUID: [u8; 16], + descriptor_set_information: DescriptorSetInformation, +} + +/// Table 10. Microsoft OS 2.0 descriptor set header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct DescriptorSetHeader { + wLength: u16, + wDescriptorType: u16, + dwWindowsVersion: u32, + wTotalLength: u16, +} + +impl DescriptorSetHeader { + /// Creates a MS OS descriptor set header. + /// + /// `windows_version` is the minimum Windows version the descriptor set can apply to. + pub fn new(windows_version: u32) -> Self { + DescriptorSetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + dwWindowsVersion: windows_version.to_le(), + wTotalLength: 0, + } + } +} + +impl Descriptor for DescriptorSetHeader { + const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for DescriptorSetHeader { + const LENGTH_OFFSET: usize = 8; +} + +/// Table 11. Configuration subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ConfigurationSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bConfigurationValue: u8, + bReserved: u8, + wTotalLength: u16, +} + +impl ConfigurationSubsetHeader { + /// Creates a configuration subset header + pub fn new(config: u8) -> Self { + ConfigurationSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bConfigurationValue: config, + bReserved: 0, + wTotalLength: 0, + } + } +} + +impl Descriptor for ConfigurationSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for ConfigurationSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + +/// Table 12. Function subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct FunctionSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bFirstInterface: InterfaceNumber, + bReserved: u8, + wSubsetLength: u16, +} + +impl FunctionSubsetHeader { + /// Creates a function subset header + pub fn new(first_interface: InterfaceNumber) -> Self { + FunctionSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bFirstInterface: first_interface, + bReserved: 0, + wSubsetLength: 0, + } + } +} + +impl Descriptor for FunctionSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for FunctionSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + +// Feature Descriptors + +/// A marker trait for feature descriptors that are valid at the device level. +pub trait DeviceLevelDescriptor: Descriptor {} + +/// A marker trait for feature descriptors that are valid at the function level. +pub trait FunctionLevelDescriptor: Descriptor {} + +/// Table 13. Microsoft OS 2.0 compatible ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CompatibleIdFeatureDescriptor { + wLength: u16, + wDescriptorType: u16, + compatibleId: [u8; 8], + subCompatibleId: [u8; 8], +} + +impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {} +impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {} + +impl Descriptor for CompatibleIdFeatureDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CompatibleIdFeatureDescriptor { + /// Creates a compatible ID feature descriptor + /// + /// The ids must be 8 ASCII bytes or fewer. + pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self { + assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8); + let mut cid = [0u8; 8]; + (&mut cid[..compatible_id.len()]).copy_from_slice(compatible_id.as_bytes()); + let mut scid = [0u8; 8]; + (&mut scid[..sub_compatible_id.len()]).copy_from_slice(sub_compatible_id.as_bytes()); + Self::new_raw(cid, scid) + } + + fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + compatibleId: compatible_id, + subCompatibleId: sub_compatible_id, + } + } +} + +/// Table 14. Microsoft OS 2.0 registry property descriptor +#[allow(non_snake_case)] +pub struct RegistryPropertyFeatureDescriptor<'a> { + name: &'a str, + data: PropertyData<'a>, +} + +/// Data values that can be encoded into a registry property descriptor +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PropertyData<'a> { + /// A registry property containing a string. + Sz(&'a str), + /// A registry property containing a string that expands environment variables. + ExpandSz(&'a str), + /// A registry property containing binary data. + Binary(&'a [u8]), + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian(u32), + /// A registry property containing a big-endian 32-bit integer. + DwordBigEndian(u32), + /// A registry property containing a string that contains a symbolic link. + Link(&'a str), + /// A registry property containing multiple strings. + RegMultiSz(&'a [&'a str]), +} + +fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize { + assert!(buf.len() >= val.len()); + buf[..val.len()].copy_from_slice(val); + val.len() +} + +fn write_utf16(val: &str, buf: &mut [u8]) -> usize { + let mut pos = 0; + for c in val.encode_utf16() { + pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) +} + +impl<'a> PropertyData<'a> { + /// Gets the `PropertyDataType` for this property value + pub fn kind(&self) -> PropertyDataType { + match self { + PropertyData::Sz(_) => PropertyDataType::Sz, + PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz, + PropertyData::Binary(_) => PropertyDataType::Binary, + PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian, + PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian, + PropertyData::Link(_) => PropertyDataType::Link, + PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz, + } + } + + /// Gets the size (in bytes) of this property value when encoded. + pub fn size(&self) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => { + core::mem::size_of::() * (val.encode_utf16().count() + 1) + } + PropertyData::Binary(val) => val.len(), + PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val), + PropertyData::RegMultiSz(val) => { + core::mem::size_of::() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::() + 1 + } + } + } + + /// Encodes the data for this property value and writes it to `buf`. + pub fn write(&self, buf: &mut [u8]) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf), + PropertyData::Binary(val) => write_bytes(val, buf), + PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf), + PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf), + PropertyData::RegMultiSz(val) => { + let mut pos = 0; + for s in *val { + pos += write_utf16(s, &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) + } + } + } +} + +/// Table 15. wPropertyDataType values for the Microsoft OS 2.0 registry property descriptor. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum PropertyDataType { + /// A registry property containing a string. + Sz = 1, + /// A registry property containing a string that expands environment variables. + ExpandSz = 2, + /// A registry property containing binary data. + Binary = 3, + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian = 4, + /// A registry property containing a big-endian 32-bit integer. + DwordBigEndian = 5, + /// A registry property containing a string that contains a symbolic link. + Link = 6, + /// A registry property containing multiple strings. + RegMultiSz = 7, +} + +impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} + +impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { + const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; + + fn size(&self) -> usize { + 10 + self.name_size() + self.data.size() + } + + fn write_to(&self, buf: &mut [u8]) { + assert!(buf.len() >= self.size(), "MS OS descriptor buffer full"); + + let mut pos = 0; + pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_utf16(self.name, &mut buf[pos..]); + pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]); + self.data.write(&mut buf[pos..]); + } +} + +impl<'a> RegistryPropertyFeatureDescriptor<'a> { + /// A registry property. + pub fn new(name: &'a str, data: PropertyData<'a>) -> Self { + Self { name, data } + } + + fn name_size(&self) -> usize { + core::mem::size_of::() * (self.name.encode_utf16().count() + 1) + } +} + +/// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct MinimumRecoveryTimeDescriptor { + wLength: u16, + wDescriptorType: u16, + bResumeRecoveryTime: u8, + bResumeSignalingTime: u8, +} + +impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {} + +impl Descriptor for MinimumRecoveryTimeDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureMinResumeTime; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl MinimumRecoveryTimeDescriptor { + /// Times are in milliseconds. + /// + /// `resume_recovery_time` must be >= 0 and <= 10. + /// `resume_signaling_time` must be >= 1 and <= 20. + pub fn new(resume_recovery_time: u8, resume_signaling_time: u8) -> Self { + assert!(resume_recovery_time <= 10); + assert!(resume_signaling_time >= 1 && resume_signaling_time <= 20); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bResumeRecoveryTime: resume_recovery_time, + bResumeSignalingTime: resume_signaling_time, + } + } +} + +/// Table 17. Microsoft OS 2.0 model ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ModelIdDescriptor { + wLength: u16, + wDescriptorType: u16, + modelId: [u8; 16], +} + +impl DeviceLevelDescriptor for ModelIdDescriptor {} + +impl Descriptor for ModelIdDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureModelId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl ModelIdDescriptor { + /// Creates a new model ID descriptor + /// + /// `model_id` should be a uuid that uniquely identifies a physical device. + pub fn new(model_id: u128) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + modelId: model_id.to_le_bytes(), + } + } +} + +/// Table 18. Microsoft OS 2.0 CCGP device descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CcgpDeviceDescriptor { + wLength: u16, + wDescriptorType: u16, +} + +impl DeviceLevelDescriptor for CcgpDeviceDescriptor {} + +impl Descriptor for CcgpDeviceDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCcgpDevice; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CcgpDeviceDescriptor { + /// Creates a new CCGP device descriptor + pub fn new() -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + } + } +} + +/// Table 19. Microsoft OS 2.0 vendor revision descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct VendorRevisionDescriptor { + wLength: u16, + wDescriptorType: u16, + /// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or + /// other MS OS descriptor. Shell set to greater than or equal to 1. + VendorRevision: u16, +} + +impl DeviceLevelDescriptor for VendorRevisionDescriptor {} +impl FunctionLevelDescriptor for VendorRevisionDescriptor {} + +impl Descriptor for VendorRevisionDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureVendorRevision; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl VendorRevisionDescriptor { + /// Creates a new vendor revision descriptor + pub fn new(revision: u16) -> Self { + assert!(revision >= 1); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + VendorRevision: revision.to_le(), + } + } +} diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs index aeab063d1..c7a47f7e4 100644 --- a/embassy-usb/src/types.rs +++ b/embassy-usb/src/types.rs @@ -1,7 +1,10 @@ +//! USB types. + /// A handle for a USB interface that contains its number. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InterfaceNumber(pub(crate) u8); +#[repr(transparent)] +pub struct InterfaceNumber(pub u8); impl InterfaceNumber { pub(crate) fn new(index: u8) -> InterfaceNumber { @@ -18,7 +21,8 @@ impl From for u8 { /// A handle for a USB string descriptor that contains its index. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct StringIndex(u8); +#[repr(transparent)] +pub struct StringIndex(pub u8); impl StringIndex { pub(crate) fn new(index: u8) -> StringIndex { diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index a5d82b601..888993255 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -8,14 +8,19 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly"] } -embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly", "nrf52840"] } +embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] } +embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot/boot" } embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot/nrf" } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" + +[features] +ed25519-dalek = ["embassy-boot/ed25519-dalek"] +ed25519-salty = ["embassy-boot/ed25519-salty"] \ No newline at end of file diff --git a/examples/boot/application/nrf/README.md b/examples/boot/application/nrf/README.md index 703377a20..9d6d20336 100644 --- a/examples/boot/application/nrf/README.md +++ b/examples/boot/application/nrf/README.md @@ -1,6 +1,6 @@ # Examples using bootloader -Example for nRF52 demonstrating the bootloader. The example consists of application binaries, 'a' +Example for nRF demonstrating the bootloader. The example consists of application binaries, 'a' which allows you to press a button to start the DFU process, and 'b' which is the updated application. @@ -20,15 +20,19 @@ application. cp memory-bl.x ../../bootloader/nrf/memory.x # Flash bootloader -cargo flash --manifest-path ../../bootloader/nrf/Cargo.toml --features embassy-nrf/nrf52840 --release --chip nRF52840_xxAA +cargo flash --manifest-path ../../bootloader/nrf/Cargo.toml --features embassy-nrf/nrf52840 --target thumbv7em-none-eabi --release --chip nRF52840_xxAA # Build 'b' -cargo build --release --bin b +cargo build --release --bin b --features embassy-nrf/nrf52840 # Generate binary for 'b' -cargo objcopy --release --bin b -- -O binary b.bin +cargo objcopy --release --bin b --features embassy-nrf/nrf52840 --target thumbv7em-none-eabi -- -O binary b.bin ``` # Flash `a` (which includes b.bin) ``` -cargo flash --release --bin a --chip nRF52840_xxAA +cargo flash --release --bin a --features embassy-nrf/nrf52840 --target thumbv7em-none-eabi --chip nRF52840_xxAA ``` + +You should then see a solid LED. Pressing button 1 will cause the DFU to be loaded by the bootloader. Upon +successfully loading, you'll see the LED flash. After 5 seconds, because there is no petting of the watchdog, +you'll see the LED go solid again. This indicates that the bootloader has reverted the update. diff --git a/examples/boot/application/nrf/memory-bl-nrf91.x b/examples/boot/application/nrf/memory-bl-nrf91.x new file mode 100644 index 000000000..14ceffa73 --- /dev/null +++ b/examples/boot/application/nrf/memory-bl-nrf91.x @@ -0,0 +1,19 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* Assumes Secure Partition Manager (SPM) flashed at the start */ + FLASH : ORIGIN = 0x00050000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00056000, LENGTH = 4K + ACTIVE : ORIGIN = 0x00057000, LENGTH = 64K + DFU : ORIGIN = 0x00067000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20018000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); + +__bootloader_active_start = ORIGIN(ACTIVE); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE); + +__bootloader_dfu_start = ORIGIN(DFU); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU); diff --git a/examples/boot/application/nrf/memory-bl.x b/examples/boot/application/nrf/memory-bl.x index 8a32b905f..257d65644 100644 --- a/examples/boot/application/nrf/memory-bl.x +++ b/examples/boot/application/nrf/memory-bl.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/application/nrf/memory-nrf91.x b/examples/boot/application/nrf/memory-nrf91.x new file mode 100644 index 000000000..2bc13c0d6 --- /dev/null +++ b/examples/boot/application/nrf/memory-nrf91.x @@ -0,0 +1,16 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* Assumes Secure Partition Manager (SPM) flashed at the start */ + BOOTLOADER : ORIGIN = 0x00050000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00056000, LENGTH = 4K + FLASH : ORIGIN = 0x00057000, LENGTH = 64K + DFU : ORIGIN = 0x00067000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20018000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); + +__bootloader_dfu_start = ORIGIN(DFU); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU); diff --git a/examples/boot/application/nrf/memory.x b/examples/boot/application/nrf/memory.x index 3a54ca460..c6926e422 100644 --- a/examples/boot/application/nrf/memory.x +++ b/examples/boot/application/nrf/memory.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K FLASH : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 7a404a914..090a05b23 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -8,6 +8,7 @@ use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::nvmc::Nvmc; +use embassy_nrf::wdt::{self, Watchdog}; use panic_reset as _; static APP_B: &[u8] = include_bytes!("../../b.bin"); @@ -15,11 +16,34 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); + 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 = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); //let mut button = Input::new(p.P1_02, Pull::Up); + // nRF91 DK + // let mut led = Output::new(p.P0_02, Level::Low, OutputDrive::Standard); + // let mut button = Input::new(p.P0_06, Pull::Up); + + // The following code block illustrates how to obtain a watchdog that is configured + // as per the existing watchdog. Ordinarily, we'd use the handle returned to "pet" the + // watchdog periodically. If we don't, and we're not going to for this example, then + // the watchdog will cause the device to reset as per its configured timeout in the bootloader. + // This helps is avoid a situation where new firmware might be bad and block our executor. + // If firmware is bad in this way then the bootloader will revert to any previous version. + let wdt_config = wdt::Config::try_new(&p.WDT).unwrap(); + let (_wdt, [_wdt_handle]) = match Watchdog::try_new(p.WDT, wdt_config) { + Ok(x) => x, + Err(_) => { + // Watchdog already active with the wrong number of handles, waiting for it to timeout... + loop { + cortex_m::asm::wfe(); + } + } + }; + let nvmc = Nvmc::new(p.NVMC); let mut nvmc = BlockingAsync::new(nvmc); diff --git a/examples/boot/application/nrf/src/bin/b.rs b/examples/boot/application/nrf/src/bin/b.rs index 1373f277d..15ebce5fa 100644 --- a/examples/boot/application/nrf/src/bin/b.rs +++ b/examples/boot/application/nrf/src/bin/b.rs @@ -12,7 +12,10 @@ use panic_reset as _; async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); - //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); + // let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); + + // nRF91 DK + // let mut led = Output::new(p.P0_02, Level::Low, OutputDrive::Standard); loop { led.set_high(); diff --git a/examples/boot/application/rp/.cargo/config.toml b/examples/boot/application/rp/.cargo/config.toml new file mode 100644 index 000000000..edbd0a867 --- /dev/null +++ b/examples/boot/application/rp/.cargo/config.toml @@ -0,0 +1,12 @@ +[unstable] +build-std = ["core"] +build-std-features = ["panic_immediate_abort"] + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml new file mode 100644 index 000000000..8d826790b --- /dev/null +++ b/examples/boot/application/rp/Cargo.toml @@ -0,0 +1,33 @@ +[package] +edition = "2021" +name = "embassy-boot-rp-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } +embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly"] } +embassy-rp = { version = "0.1.0", path = "../../../../embassy-rp", features = ["time-driver", "unstable-traits", "nightly"] } +embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot/rp" } +embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } + +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"], optional = true } +panic-reset = { version = "0.1.1", optional = true } +embedded-hal = { version = "0.2.6" } + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +default = ["panic-reset"] +debug = [ + "embassy-rp/defmt", + "embassy-boot-rp/defmt", + "panic-probe" +] + +[profile.release] +debug = true diff --git a/examples/boot/application/rp/README.md b/examples/boot/application/rp/README.md new file mode 100644 index 000000000..41304c526 --- /dev/null +++ b/examples/boot/application/rp/README.md @@ -0,0 +1,28 @@ +# Examples using bootloader + +Example for Raspberry Pi Pico demonstrating the bootloader. The example consists of application binaries, 'a' +which waits for 5 seconds before flashing the 'b' binary, which blinks the LED. + +NOTE: The 'b' binary does not mark the new binary as active, so if you reset the device, it will roll back to the 'a' binary before automatically updating it again. + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-rp` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/rp/Cargo.toml --release --chip RP2040 + +# Build 'b' +cargo build --release --bin b + +# Generate binary for 'b' +cargo objcopy --release --bin b -- -O binary b.bin + +# Flash `a` (which includes b.bin) +cargo flash --release --bin a --chip RP2040 +``` diff --git a/examples/boot/application/rp/build.rs b/examples/boot/application/rp/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/boot/application/rp/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/boot/application/rp/memory.x b/examples/boot/application/rp/memory.x new file mode 100644 index 000000000..c19473114 --- /dev/null +++ b/examples/boot/application/rp/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K + FLASH : ORIGIN = 0x10007000, LENGTH = 512K + DFU : ORIGIN = 0x10087000, LENGTH = 516K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOT2); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOT2); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOT2); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOT2); diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs new file mode 100644 index 000000000..e3ac634c2 --- /dev/null +++ b/examples/boot/application/rp/src/bin/a.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; +use embassy_boot_rp::*; +use embassy_executor::Spawner; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::watchdog::Watchdog; +use embassy_time::{Duration, Timer}; +#[cfg(feature = "panic-probe")] +use panic_probe as _; +#[cfg(feature = "panic-reset")] +use panic_reset as _; + +static APP_B: &[u8] = include_bytes!("../../b.bin"); +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::main] +async fn main(_s: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Override bootloader watchdog + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + let mut flash: Flash<_, FLASH_SIZE> = Flash::new(p.FLASH); + + let mut updater = FirmwareUpdater::default(); + + Timer::after(Duration::from_secs(5)).await; + watchdog.feed(); + led.set_high(); + let mut offset = 0; + let mut buf: AlignedBuffer<4096> = AlignedBuffer([0; 4096]); + defmt::info!("preparing update"); + let mut writer = updater + .prepare_update_blocking(&mut flash) + .map_err(|e| defmt::warn!("E: {:?}", defmt::Debug2Format(&e))) + .unwrap(); + defmt::info!("writer created, starting write"); + for chunk in APP_B.chunks(4096) { + buf.0[..chunk.len()].copy_from_slice(chunk); + defmt::info!("writing block at offset {}", offset); + writer + .write_block_blocking(offset, &buf.0[..], &mut flash, 256) + .unwrap(); + offset += chunk.len(); + } + watchdog.feed(); + defmt::info!("firmware written, marking update"); + updater.mark_updated_blocking(&mut flash, &mut buf.0[..1]).unwrap(); + Timer::after(Duration::from_secs(2)).await; + led.set_low(); + defmt::info!("update marked, resetting"); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/examples/boot/application/rp/src/bin/b.rs b/examples/boot/application/rp/src/bin/b.rs new file mode 100644 index 000000000..47dec329c --- /dev/null +++ b/examples/boot/application/rp/src/bin/b.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_reset as _}; + +#[embassy_executor::main] +async fn main(_s: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + led.set_high(); + Timer::after(Duration::from_millis(100)).await; + + led.set_low(); + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 3a1843562..aa279fb76 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -13,7 +13,7 @@ embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index 8d9c4490e..1ec0643a6 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -13,7 +13,7 @@ embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index b4314aa72..a4eefe2a5 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -13,7 +13,7 @@ embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index a17d336a6..36eada29b 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -13,7 +13,7 @@ embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index 683f2c860..67efda748 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -13,7 +13,7 @@ embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index b879c0d76..4b2e02dd2 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -13,7 +13,7 @@ embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index e3bc0e49c..fecbfc51d 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -13,7 +13,7 @@ embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index b417a40d1..8a6f53643 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } embassy-nrf = { path = "../../../../embassy-nrf", default-features = false, features = ["nightly"] } embassy-boot-nrf = { path = "../../../../embassy-boot/nrf", default-features = false } diff --git a/examples/boot/bootloader/nrf/memory-bm.x b/examples/boot/bootloader/nrf/memory-bm.x index 8a32b905f..257d65644 100644 --- a/examples/boot/bootloader/nrf/memory-bm.x +++ b/examples/boot/bootloader/nrf/memory-bm.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/bootloader/nrf/memory.x b/examples/boot/bootloader/nrf/memory.x index 8a32b905f..257d65644 100644 --- a/examples/boot/bootloader/nrf/memory.x +++ b/examples/boot/bootloader/nrf/memory.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs index 8266206b3..aca3b857a 100644 --- a/examples/boot/bootloader/nrf/src/main.rs +++ b/examples/boot/bootloader/nrf/src/main.rs @@ -6,6 +6,7 @@ use cortex_m_rt::{entry, exception}; use defmt_rtt as _; use embassy_boot_nrf::*; use embassy_nrf::nvmc::Nvmc; +use embassy_nrf::wdt; #[entry] fn main() -> ! { @@ -20,8 +21,14 @@ fn main() -> ! { */ let mut bl = BootLoader::default(); + + let mut wdt_config = wdt::Config::default(); + wdt_config.timeout_ticks = 32768 * 5; // timeout seconds + wdt_config.run_during_sleep = true; + wdt_config.run_during_debug_halt = false; + let start = bl.prepare(&mut SingleFlashConfig::new(&mut BootFlash::<_, 4096>::new( - WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, 5), + WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config), ))); unsafe { bl.load(start) } } diff --git a/examples/boot/bootloader/rp/.cargo/config.toml b/examples/boot/bootloader/rp/.cargo/config.toml new file mode 100644 index 000000000..18bd4dfe8 --- /dev/null +++ b/examples/boot/bootloader/rp/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml new file mode 100644 index 000000000..c0b576cff --- /dev/null +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "rp-bootloader-example" +version = "0.1.0" +description = "Example bootloader for RP2040 chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-rp = { path = "../../../../embassy-rp", default-features = false, features = ["nightly"] } +embassy-boot-rp = { path = "../../../../embassy-boot/rp", default-features = false } +embassy-time = { path = "../../../../embassy-time", features = ["nightly"] } + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.0" +embedded-storage-async = "0.3.0" +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-boot-rp/defmt", + "embassy-rp/defmt", +] +debug = ["defmt-rtt", "defmt"] + +[profile.release] +debug = true diff --git a/examples/boot/bootloader/rp/README.md b/examples/boot/bootloader/rp/README.md new file mode 100644 index 000000000..064e87273 --- /dev/null +++ b/examples/boot/bootloader/rp/README.md @@ -0,0 +1,17 @@ +# Bootloader for RP2040 + +The bootloader uses `embassy-boot` to interact with the flash. + +# Usage + +Flashing the bootloader + +``` +cargo flash --release --chip RP2040 +``` + +To debug, use `cargo run` and enable the debug feature flag + +``` rust +cargo run --release --features debug +``` diff --git a/examples/boot/bootloader/rp/build.rs b/examples/boot/bootloader/rp/build.rs new file mode 100644 index 000000000..c201704ad --- /dev/null +++ b/examples/boot/bootloader/rp/build.rs @@ -0,0 +1,28 @@ +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=-Tlink-rp.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/examples/boot/bootloader/rp/memory.x b/examples/boot/bootloader/rp/memory.x new file mode 100644 index 000000000..d6ef38469 --- /dev/null +++ b/examples/boot/bootloader/rp/memory.x @@ -0,0 +1,19 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x10007000, LENGTH = 512K + DFU : ORIGIN = 0x10087000, LENGTH = 516K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOT2); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOT2); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(BOOT2); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(BOOT2); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOT2); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOT2); diff --git a/examples/boot/bootloader/rp/src/main.rs b/examples/boot/bootloader/rp/src/main.rs new file mode 100644 index 000000000..fb7f0522b --- /dev/null +++ b/examples/boot/bootloader/rp/src/main.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_rp::*; +use embassy_rp::flash::ERASE_SIZE; +use embassy_time::Duration; + +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + + // Uncomment this if you are debugging the bootloader with debugger/RTT attached, + // as it prevents a hard fault when accessing flash 'too early' after boot. + /* + for i in 0..10000000 { + cortex_m::asm::nop(); + } + */ + + let mut bl: BootLoader = BootLoader::default(); + let flash = WatchdogFlash::::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); + let mut flash = BootFlash::<_, ERASE_SIZE>::new(flash); + let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash)); + core::mem::drop(flash); + + unsafe { bl.load(start) } +} + +#[no_mangle] +#[cfg_attr(target_os = "none", link_section = ".HardFault.user")] +unsafe extern "C" fn HardFault() { + cortex_m::peripheral::SCB::sys_reset(); +} + +#[exception] +unsafe fn DefaultHandler(_: i16) -> ! { + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16; + + panic!("DefaultHandler #{:?}", irqn); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + cortex_m::asm::udf(); +} diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index 4ddd1c99c..be659e02a 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", default-features = false, features = ["nightly"] } embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32", default-features = false } diff --git a/examples/nrf/src/bin/usb_ethernet.rs b/examples/nrf/src/bin/usb_ethernet.rs deleted file mode 100644 index de93a2b45..000000000 --- a/examples/nrf/src/bin/usb_ethernet.rs +++ /dev/null @@ -1,274 +0,0 @@ -#![no_std] -#![no_main] -#![feature(type_alias_impl_trait)] - -use core::mem; -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; - -use defmt::*; -use embassy_executor::Spawner; -use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; -use embassy_nrf::rng::Rng; -use embassy_nrf::usb::{Driver, PowerUsb}; -use embassy_nrf::{interrupt, pac, peripherals}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; -use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; -use embassy_usb::{Builder, Config, UsbDevice}; -use embedded_io::asynch::Write; -use static_cell::StaticCell; -use {defmt_rtt as _, panic_probe as _}; - -type MyDriver = Driver<'static, peripherals::USBD, PowerUsb>; - -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} - -#[embassy_executor::task] -async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { - device.run().await -} - -#[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } -} - -#[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await -} - -#[embassy_executor::main] -async fn main(spawner: Spawner) { - let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; - - info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} - - // Create the driver, from the HAL. - let irq = interrupt::take!(USBD); - let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); - - // Create embassy-usb Config - let mut config = Config::new(0xc0de, 0xcafe); - config.manufacturer = Some("Embassy"); - config.product = Some("USB-Ethernet example"); - config.serial_number = Some("12345678"); - config.max_power = 100; - config.max_packet_size_0 = 64; - - // Required for Windows support. - config.composite_with_iads = true; - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - - // Create embassy-usb DeviceBuilder using the driver and config. - let mut builder = Builder::new( - driver, - config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, - None, - ); - - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - - // Our MAC addr. - let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; - // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. - let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; - - // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); - - // Build the builder. - let usb = builder.build(); - - unwrap!(spawner.spawn(usb_task(usb))); - - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); - - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { - // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), - // dns_servers: Vec::new(), - // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), - //}); - - // Generate random seed - let mut rng = Rng::new(p.RNG, interrupt::take!(RNG)); - let mut seed = [0; 8]; - rng.blocking_fill_bytes(&mut seed); - let seed = u64::from_le_bytes(seed); - - // Init network stack - let device = Device { mac_addr: our_mac_addr }; - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); - - unwrap!(spawner.spawn(net_task(stack))); - - // And now we can use it! - - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - let mut buf = [0; 4096]; - - loop { - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); - - info!("Listening on TCP:1234..."); - if let Err(e) = socket.accept(1234).await { - warn!("accept error: {:?}", e); - continue; - } - - info!("Received connection from {:?}", socket.remote_endpoint()); - - loop { - let n = match socket.read(&mut buf).await { - Ok(0) => { - warn!("read EOF"); - break; - } - Ok(n) => n, - Err(e) => { - warn!("read error: {:?}", e); - break; - } - }; - - info!("rxd {:02x}", &buf[..n]); - - match socket.write_all(&buf[..n]).await { - Ok(()) => {} - Err(e) => { - warn!("write error: {:?}", e); - break; - } - }; - } - } -} - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} diff --git a/examples/nrf/.cargo/config.toml b/examples/nrf52840/.cargo/config.toml similarity index 100% rename from examples/nrf/.cargo/config.toml rename to examples/nrf52840/.cargo/config.toml diff --git a/examples/nrf/Cargo.toml b/examples/nrf52840/Cargo.toml similarity index 65% rename from examples/nrf/Cargo.toml rename to examples/nrf52840/Cargo.toml index 9ebd04845..cfdda076e 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -1,25 +1,31 @@ [package] edition = "2021" -name = "embassy-nrf-examples" +name = "embassy-nrf52840-examples" version = "0.1.0" license = "MIT OR Apache-2.0" [features] default = ["nightly"] -nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net"] +msos-descriptor = ["embassy-usb/msos-descriptor"] +nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net", + "embassy-lora", "lorawan-device", "lorawan"] [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", 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"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } -embedded-io = "0.3.0" +embedded-io = "0.4.0" +embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx126x", "time", "defmt"], optional = true } + +lorawan-device = { version = "0.8.0", default-features = false, features = ["async"], optional = true } +lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" static_cell = "1.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } @@ -30,3 +36,7 @@ rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" usbd-hid = "0.6.0" serde = { version = "1.0.136", default-features = false } + +[[bin]] +name = "usb_serial_winusb" +required-features = ["msos-descriptor"] diff --git a/examples/nrf52840/build.rs b/examples/nrf52840/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/nrf52840/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/nrf52840/memory.x b/examples/nrf52840/memory.x new file mode 100644 index 000000000..9b04edec0 --- /dev/null +++ b/examples/nrf52840/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* These values correspond to the NRF52840 with Softdevices S140 7.0.1 */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/examples/nrf/src/bin/awaitable_timer.rs b/examples/nrf52840/src/bin/awaitable_timer.rs similarity index 100% rename from examples/nrf/src/bin/awaitable_timer.rs rename to examples/nrf52840/src/bin/awaitable_timer.rs diff --git a/examples/nrf/src/bin/blinky.rs b/examples/nrf52840/src/bin/blinky.rs similarity index 100% rename from examples/nrf/src/bin/blinky.rs rename to examples/nrf52840/src/bin/blinky.rs diff --git a/examples/nrf/src/bin/buffered_uart.rs b/examples/nrf52840/src/bin/buffered_uart.rs similarity index 100% rename from examples/nrf/src/bin/buffered_uart.rs rename to examples/nrf52840/src/bin/buffered_uart.rs diff --git a/examples/nrf/src/bin/channel.rs b/examples/nrf52840/src/bin/channel.rs similarity index 100% rename from examples/nrf/src/bin/channel.rs rename to examples/nrf52840/src/bin/channel.rs diff --git a/examples/nrf/src/bin/channel_sender_receiver.rs b/examples/nrf52840/src/bin/channel_sender_receiver.rs similarity index 100% rename from examples/nrf/src/bin/channel_sender_receiver.rs rename to examples/nrf52840/src/bin/channel_sender_receiver.rs diff --git a/examples/nrf/src/bin/executor_fairness_test.rs b/examples/nrf52840/src/bin/executor_fairness_test.rs similarity index 100% rename from examples/nrf/src/bin/executor_fairness_test.rs rename to examples/nrf52840/src/bin/executor_fairness_test.rs diff --git a/examples/nrf/src/bin/gpiote_channel.rs b/examples/nrf52840/src/bin/gpiote_channel.rs similarity index 100% rename from examples/nrf/src/bin/gpiote_channel.rs rename to examples/nrf52840/src/bin/gpiote_channel.rs diff --git a/examples/nrf/src/bin/gpiote_port.rs b/examples/nrf52840/src/bin/gpiote_port.rs similarity index 100% rename from examples/nrf/src/bin/gpiote_port.rs rename to examples/nrf52840/src/bin/gpiote_port.rs diff --git a/examples/nrf52840/src/bin/i2s_effect.rs b/examples/nrf52840/src/bin/i2s_effect.rs new file mode 100644 index 000000000..52d46e4f9 --- /dev/null +++ b/examples/nrf52840/src/bin/i2s_effect.rs @@ -0,0 +1,117 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::f32::consts::PI; + +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, MasterClock, MultiBuffering, Sample as _, SampleWidth, I2S}; +use embassy_nrf::interrupt; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_BUFFERS: usize = 2; +const NUM_SAMPLES: usize = 4; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; + + let irq = interrupt::take!(I2S); + let buffers_out = MultiBuffering::::new(); + let buffers_in = MultiBuffering::::new(); + let mut full_duplex_stream = I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).full_duplex( + p.P0_29, + p.P0_28, + buffers_out, + buffers_in, + ); + + let mut modulator = SineOsc::new(); + modulator.set_frequency(8.0, 1.0 / sample_rate as f32); + modulator.set_amplitude(1.0); + + full_duplex_stream.start().await.expect("I2S Start"); + + loop { + let (buff_out, buff_in) = full_duplex_stream.buffers(); + for i in 0..NUM_SAMPLES { + let modulation = (Sample::SCALE as f32 * bipolar_to_unipolar(modulator.generate())) as Sample; + buff_out[i] = buff_in[i] * modulation; + } + + if let Err(err) = full_duplex_stream.send_and_receive().await { + error!("{}", err); + } + } +} + +struct SineOsc { + amplitude: f32, + modulo: f32, + phase_inc: f32, +} + +impl SineOsc { + const B: f32 = 4.0 / PI; + const C: f32 = -4.0 / (PI * PI); + const P: f32 = 0.225; + + pub fn new() -> Self { + Self { + amplitude: 1.0, + modulo: 0.0, + phase_inc: 0.0, + } + } + + pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { + self.phase_inc = freq * inv_sample_rate; + } + + pub fn set_amplitude(&mut self, amplitude: f32) { + self.amplitude = amplitude; + } + + pub fn generate(&mut self) -> f32 { + let signal = self.parabolic_sin(self.modulo); + self.modulo += self.phase_inc; + if self.modulo < 0.0 { + self.modulo += 1.0; + } else if self.modulo > 1.0 { + self.modulo -= 1.0; + } + signal * self.amplitude + } + + fn parabolic_sin(&mut self, modulo: f32) -> f32 { + let angle = PI - modulo * 2.0 * PI; + let y = Self::B * angle + Self::C * angle * abs(angle); + Self::P * (y * abs(y) - y) + y + } +} + +#[inline] +fn abs(value: f32) -> f32 { + if value < 0.0 { + -value + } else { + value + } +} + +#[inline] +fn bipolar_to_unipolar(value: f32) -> f32 { + (value + 1.0) / 2.0 +} diff --git a/examples/nrf52840/src/bin/i2s_monitor.rs b/examples/nrf52840/src/bin/i2s_monitor.rs new file mode 100644 index 000000000..5ebfd9542 --- /dev/null +++ b/examples/nrf52840/src/bin/i2s_monitor.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{debug, error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; +use embassy_nrf::interrupt; +use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 500; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; + + let irq = interrupt::take!(I2S); + let buffers = DoubleBuffering::::new(); + let mut input_stream = + I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).input(p.P0_29, buffers); + + // Configure the PWM to use the pins corresponding to the RGB leds + let mut pwm = SimplePwm::new_3ch(p.PWM0, p.P0_23, p.P0_22, p.P0_24); + pwm.set_prescaler(Prescaler::Div1); + pwm.set_max_duty(255); + + let mut rms_online = RmsOnline::::default(); + + input_stream.start().await.expect("I2S Start"); + + loop { + let rms = rms_online.process(input_stream.buffer()); + let rgb = rgb_from_rms(rms); + + debug!("RMS: {}, RGB: {:?}", rms, rgb); + for i in 0..3 { + pwm.set_duty(i, rgb[i].into()); + } + + if let Err(err) = input_stream.receive().await { + error!("{}", err); + } + } +} + +/// RMS from 0.0 until 0.75 will give green with a proportional intensity +/// RMS from 0.75 until 0.9 will give a blend between orange and red proportionally to the intensity +/// RMS above 0.9 will give a red with a proportional intensity +fn rgb_from_rms(rms: f32) -> [u8; 3] { + if rms < 0.75 { + let intensity = rms / 0.75; + [0, (intensity * 165.0) as u8, 0] + } else if rms < 0.9 { + let intensity = (rms - 0.75) / 0.15; + [200, 165 - (165.0 * intensity) as u8, 0] + } else { + let intensity = (rms - 0.9) / 0.1; + [200 + (55.0 * intensity) as u8, 0, 0] + } +} + +pub struct RmsOnline { + pub squares: [f32; N], + pub head: usize, +} + +impl Default for RmsOnline { + fn default() -> Self { + RmsOnline { + squares: [0.0; N], + head: 0, + } + } +} + +impl RmsOnline { + pub fn reset(&mut self) { + self.squares = [0.0; N]; + self.head = 0; + } + + pub fn process(&mut self, buf: &[Sample]) -> f32 { + buf.iter() + .for_each(|sample| self.push(*sample as f32 / Sample::SCALE as f32)); + + let sum_of_squares = self.squares.iter().fold(0.0, |acc, v| acc + *v); + Self::approx_sqrt(sum_of_squares / N as f32) + } + + pub fn push(&mut self, signal: f32) { + let square = signal * signal; + self.squares[self.head] = square; + self.head = (self.head + 1) % N; + } + + /// Approximated sqrt taken from [micromath] + /// + /// [micromath]: https://docs.rs/micromath/latest/src/micromath/float/sqrt.rs.html#11-17 + /// + fn approx_sqrt(value: f32) -> f32 { + f32::from_bits((value.to_bits() + 0x3f80_0000) >> 1) + } +} diff --git a/examples/nrf52840/src/bin/i2s_waveform.rs b/examples/nrf52840/src/bin/i2s_waveform.rs new file mode 100644 index 000000000..eda930677 --- /dev/null +++ b/examples/nrf52840/src/bin/i2s_waveform.rs @@ -0,0 +1,151 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::f32::consts::PI; + +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; +use embassy_nrf::interrupt; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 50; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; + + let irq = interrupt::take!(I2S); + let buffers = DoubleBuffering::::new(); + let mut output_stream = + I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).output(p.P0_28, buffers); + + let mut waveform = Waveform::new(1.0 / sample_rate as f32); + + waveform.process(output_stream.buffer()); + + output_stream.start().await.expect("I2S Start"); + + loop { + waveform.process(output_stream.buffer()); + + if let Err(err) = output_stream.send().await { + error!("{}", err); + } + } +} + +struct Waveform { + inv_sample_rate: f32, + carrier: SineOsc, + freq_mod: SineOsc, + amp_mod: SineOsc, +} + +impl Waveform { + fn new(inv_sample_rate: f32) -> Self { + let mut carrier = SineOsc::new(); + carrier.set_frequency(110.0, inv_sample_rate); + + let mut freq_mod = SineOsc::new(); + freq_mod.set_frequency(1.0, inv_sample_rate); + freq_mod.set_amplitude(1.0); + + let mut amp_mod = SineOsc::new(); + amp_mod.set_frequency(16.0, inv_sample_rate); + amp_mod.set_amplitude(0.5); + + Self { + inv_sample_rate, + carrier, + freq_mod, + amp_mod, + } + } + + fn process(&mut self, buf: &mut [Sample]) { + for sample in buf.chunks_mut(1) { + let freq_modulation = bipolar_to_unipolar(self.freq_mod.generate()); + self.carrier + .set_frequency(110.0 + 440.0 * freq_modulation, self.inv_sample_rate); + + let amp_modulation = bipolar_to_unipolar(self.amp_mod.generate()); + self.carrier.set_amplitude(amp_modulation); + + let signal = self.carrier.generate(); + + sample[0] = (Sample::SCALE as f32 * signal) as Sample; + } + } +} + +struct SineOsc { + amplitude: f32, + modulo: f32, + phase_inc: f32, +} + +impl SineOsc { + const B: f32 = 4.0 / PI; + const C: f32 = -4.0 / (PI * PI); + const P: f32 = 0.225; + + pub fn new() -> Self { + Self { + amplitude: 1.0, + modulo: 0.0, + phase_inc: 0.0, + } + } + + pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { + self.phase_inc = freq * inv_sample_rate; + } + + pub fn set_amplitude(&mut self, amplitude: f32) { + self.amplitude = amplitude; + } + + pub fn generate(&mut self) -> f32 { + let signal = self.parabolic_sin(self.modulo); + self.modulo += self.phase_inc; + if self.modulo < 0.0 { + self.modulo += 1.0; + } else if self.modulo > 1.0 { + self.modulo -= 1.0; + } + signal * self.amplitude + } + + fn parabolic_sin(&mut self, modulo: f32) -> f32 { + let angle = PI - modulo * 2.0 * PI; + let y = Self::B * angle + Self::C * angle * abs(angle); + Self::P * (y * abs(y) - y) + y + } +} + +#[inline] +fn abs(value: f32) -> f32 { + if value < 0.0 { + -value + } else { + value + } +} + +#[inline] +fn bipolar_to_unipolar(value: f32) -> f32 { + (value + 1.0) / 2.0 +} diff --git a/examples/nrf52840/src/bin/lora_p2p_report.rs b/examples/nrf52840/src/bin/lora_p2p_report.rs new file mode 100644 index 000000000..d512b83f6 --- /dev/null +++ b/examples/nrf52840/src/bin/lora_p2p_report.rs @@ -0,0 +1,78 @@ +//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. +//! Other nrf/sx126x combinations may work with appropriate pin modifications. +//! It demonstates LORA P2P functionality in conjunction with example lora_p2p_sense.rs. +#![no_std] +#![no_main] +#![macro_use] +#![allow(dead_code)] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::sx126x::*; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; +use embassy_nrf::{interrupt, spim}; +use embassy_time::{Duration, Timer}; +use lorawan_device::async_device::radio::{Bandwidth, CodingRate, PhyRxTx, RfConfig, SpreadingFactor}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut spi_config = spim::Config::default(); + spi_config.frequency = spim::Frequency::M16; + + let mut radio = { + let irq = interrupt::take!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + let spim = spim::Spim::new(p.TWISPI1, irq, p.P1_11, p.P1_13, p.P1_12, spi_config); + + let cs = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard); + let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard); + let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); + let busy = Input::new(p.P1_14.degrade(), Pull::Down); + let antenna_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard); + let antenna_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard); + + match Sx126xRadio::new(spim, cs, reset, antenna_rx, antenna_tx, dio1, busy, false).await { + Ok(r) => r, + Err(err) => { + info!("Sx126xRadio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard); + let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + loop { + let rf_config = RfConfig { + frequency: 903900000, // channel in Hz + bandwidth: Bandwidth::_250KHz, + spreading_factor: SpreadingFactor::_10, + coding_rate: CodingRate::_4_8, + }; + + let mut buffer = [00u8; 100]; + + // P2P receive + match radio.rx(rf_config, &mut buffer).await { + Ok((buffer_len, rx_quality)) => info!( + "RX received = {:?} with length = {} rssi = {} snr = {}", + &buffer[0..buffer_len], + buffer_len, + rx_quality.rssi(), + rx_quality.snr() + ), + Err(err) => info!("RX error = {}", err), + } + + debug_indicator.set_high(); + Timer::after(Duration::from_secs(2)).await; + debug_indicator.set_low(); + } +} diff --git a/examples/nrf52840/src/bin/lora_p2p_sense.rs b/examples/nrf52840/src/bin/lora_p2p_sense.rs new file mode 100644 index 000000000..b9768874b --- /dev/null +++ b/examples/nrf52840/src/bin/lora_p2p_sense.rs @@ -0,0 +1,125 @@ +//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. +//! Other nrf/sx126x combinations may work with appropriate pin modifications. +//! It demonstates LORA P2P functionality in conjunction with example lora_p2p_report.rs. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] +#![feature(alloc_error_handler)] +#![allow(incomplete_features)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::sx126x::*; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; +use embassy_nrf::{interrupt, spim}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::pubsub::{PubSubChannel, Publisher}; +use embassy_time::{Duration, Timer}; +use lorawan_device::async_device::radio::{Bandwidth, CodingRate, PhyRxTx, RfConfig, SpreadingFactor, TxConfig}; +use {defmt_rtt as _, panic_probe as _, panic_probe as _}; + +// Message bus: queue of 2, 1 subscriber (Lora P2P), 2 publishers (temperature, motion detection) +static MESSAGE_BUS: PubSubChannel = PubSubChannel::new(); + +#[derive(Clone, defmt::Format)] +enum Message { + Temperature(i32), + MotionDetected, +} + +#[embassy_executor::task] +async fn temperature_task(publisher: Publisher<'static, CriticalSectionRawMutex, Message, 2, 1, 2>) { + // Publish a fake temperature every 43 seconds, minimizing LORA traffic. + loop { + Timer::after(Duration::from_secs(43)).await; + publisher.publish(Message::Temperature(9)).await; + } +} + +#[embassy_executor::task] +async fn motion_detection_task(publisher: Publisher<'static, CriticalSectionRawMutex, Message, 2, 1, 2>) { + // Publish a fake motion detection every 79 seconds, minimizing LORA traffic. + loop { + Timer::after(Duration::from_secs(79)).await; + publisher.publish(Message::MotionDetected).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + // set up to funnel temperature and motion detection events to the Lora Tx task + let mut lora_tx_subscriber = unwrap!(MESSAGE_BUS.subscriber()); + let temperature_publisher = unwrap!(MESSAGE_BUS.publisher()); + let motion_detection_publisher = unwrap!(MESSAGE_BUS.publisher()); + + let mut spi_config = spim::Config::default(); + spi_config.frequency = spim::Frequency::M16; + + let mut radio = { + let irq = interrupt::take!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + let spim = spim::Spim::new(p.TWISPI1, irq, p.P1_11, p.P1_13, p.P1_12, spi_config); + + let cs = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard); + let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard); + let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); + let busy = Input::new(p.P1_14.degrade(), Pull::Down); + let antenna_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard); + let antenna_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard); + + match Sx126xRadio::new(spim, cs, reset, antenna_rx, antenna_tx, dio1, busy, false).await { + Ok(r) => r, + Err(err) => { + info!("Sx126xRadio error = {}", err); + return; + } + } + }; + + let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + match radio.lora.sleep().await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } + + unwrap!(spawner.spawn(temperature_task(temperature_publisher))); + unwrap!(spawner.spawn(motion_detection_task(motion_detection_publisher))); + + loop { + let message = lora_tx_subscriber.next_message_pure().await; + + let tx_config = TxConfig { + // 11 byte maximum payload for Bandwidth 125 and SF 10 + pw: 10, // up to 20 + rf: RfConfig { + frequency: 903900000, // channel in Hz, not MHz + bandwidth: Bandwidth::_250KHz, + spreading_factor: SpreadingFactor::_10, + coding_rate: CodingRate::_4_8, + }, + }; + + let mut buffer = [0x00u8]; + match message { + Message::Temperature(temperature) => buffer[0] = temperature as u8, + Message::MotionDetected => buffer[0] = 0x01u8, + }; + + // unencrypted + match radio.tx(tx_config, &buffer).await { + Ok(ret_val) => info!("TX ret_val = {}", ret_val), + Err(err) => info!("TX error = {}", err), + } + + match radio.lora.sleep().await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } + } +} diff --git a/examples/nrf52840/src/bin/manually_create_executor.rs b/examples/nrf52840/src/bin/manually_create_executor.rs new file mode 100644 index 000000000..12ce660f9 --- /dev/null +++ b/examples/nrf52840/src/bin/manually_create_executor.rs @@ -0,0 +1,49 @@ +// This example showcases how to manually create an executor. +// This is what the #[embassy::main] macro does behind the scenes. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run1() { + loop { + info!("BIG INFREQUENT TICK"); + Timer::after(Duration::from_ticks(64000)).await; + } +} + +#[embassy_executor::task] +async fn run2() { + loop { + info!("tick"); + Timer::after(Duration::from_ticks(13000)).await; + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_nrf::init(Default::default()); + + // Create the executor and put it in a StaticCell, because `run` needs `&'static mut Executor`. + let executor = EXECUTOR.init(Executor::new()); + + // Run it. + // `run` calls the closure then runs the executor forever. It never returns. + executor.run(|spawner| { + // Here we get access to a spawner to spawn the initial tasks. + unwrap!(spawner.spawn(run1())); + unwrap!(spawner.spawn(run2())); + }); +} diff --git a/examples/nrf/src/bin/multiprio.rs b/examples/nrf52840/src/bin/multiprio.rs similarity index 100% rename from examples/nrf/src/bin/multiprio.rs rename to examples/nrf52840/src/bin/multiprio.rs diff --git a/examples/nrf/src/bin/mutex.rs b/examples/nrf52840/src/bin/mutex.rs similarity index 100% rename from examples/nrf/src/bin/mutex.rs rename to examples/nrf52840/src/bin/mutex.rs diff --git a/examples/nrf/src/bin/nvmc.rs b/examples/nrf52840/src/bin/nvmc.rs similarity index 100% rename from examples/nrf/src/bin/nvmc.rs rename to examples/nrf52840/src/bin/nvmc.rs diff --git a/examples/nrf52840/src/bin/pdm.rs b/examples/nrf52840/src/bin/pdm.rs new file mode 100644 index 000000000..7388580fb --- /dev/null +++ b/examples/nrf52840/src/bin/pdm.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::interrupt; +use embassy_nrf::pdm::{Config, Pdm}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let p = embassy_nrf::init(Default::default()); + let config = Config::default(); + let mut pdm = Pdm::new(p.PDM, interrupt::take!(PDM), p.P0_01, p.P0_00, config); + + loop { + pdm.start().await; + + // wait some time till the microphon settled + Timer::after(Duration::from_millis(1000)).await; + + const SAMPLES: usize = 2048; + let mut buf = [0i16; SAMPLES]; + pdm.sample(&mut buf).await.unwrap(); + + info!("samples: {:?}", &buf); + + pdm.stop().await; + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/examples/nrf/src/bin/ppi.rs b/examples/nrf52840/src/bin/ppi.rs similarity index 100% rename from examples/nrf/src/bin/ppi.rs rename to examples/nrf52840/src/bin/ppi.rs diff --git a/examples/nrf/src/bin/pubsub.rs b/examples/nrf52840/src/bin/pubsub.rs similarity index 100% rename from examples/nrf/src/bin/pubsub.rs rename to examples/nrf52840/src/bin/pubsub.rs diff --git a/examples/nrf/src/bin/pwm.rs b/examples/nrf52840/src/bin/pwm.rs similarity index 100% rename from examples/nrf/src/bin/pwm.rs rename to examples/nrf52840/src/bin/pwm.rs diff --git a/examples/nrf/src/bin/pwm_double_sequence.rs b/examples/nrf52840/src/bin/pwm_double_sequence.rs similarity index 100% rename from examples/nrf/src/bin/pwm_double_sequence.rs rename to examples/nrf52840/src/bin/pwm_double_sequence.rs diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf52840/src/bin/pwm_sequence.rs similarity index 100% rename from examples/nrf/src/bin/pwm_sequence.rs rename to examples/nrf52840/src/bin/pwm_sequence.rs diff --git a/examples/nrf/src/bin/pwm_sequence_ppi.rs b/examples/nrf52840/src/bin/pwm_sequence_ppi.rs similarity index 100% rename from examples/nrf/src/bin/pwm_sequence_ppi.rs rename to examples/nrf52840/src/bin/pwm_sequence_ppi.rs diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs similarity index 100% rename from examples/nrf/src/bin/pwm_sequence_ws2812b.rs rename to examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs diff --git a/examples/nrf/src/bin/pwm_servo.rs b/examples/nrf52840/src/bin/pwm_servo.rs similarity index 100% rename from examples/nrf/src/bin/pwm_servo.rs rename to examples/nrf52840/src/bin/pwm_servo.rs diff --git a/examples/nrf/src/bin/qdec.rs b/examples/nrf52840/src/bin/qdec.rs similarity index 100% rename from examples/nrf/src/bin/qdec.rs rename to examples/nrf52840/src/bin/qdec.rs diff --git a/examples/nrf/src/bin/qspi.rs b/examples/nrf52840/src/bin/qspi.rs similarity index 100% rename from examples/nrf/src/bin/qspi.rs rename to examples/nrf52840/src/bin/qspi.rs diff --git a/examples/nrf/src/bin/qspi_lowpower.rs b/examples/nrf52840/src/bin/qspi_lowpower.rs similarity index 100% rename from examples/nrf/src/bin/qspi_lowpower.rs rename to examples/nrf52840/src/bin/qspi_lowpower.rs diff --git a/examples/nrf/src/bin/raw_spawn.rs b/examples/nrf52840/src/bin/raw_spawn.rs similarity index 100% rename from examples/nrf/src/bin/raw_spawn.rs rename to examples/nrf52840/src/bin/raw_spawn.rs diff --git a/examples/nrf/src/bin/rng.rs b/examples/nrf52840/src/bin/rng.rs similarity index 100% rename from examples/nrf/src/bin/rng.rs rename to examples/nrf52840/src/bin/rng.rs diff --git a/examples/nrf/src/bin/saadc.rs b/examples/nrf52840/src/bin/saadc.rs similarity index 100% rename from examples/nrf/src/bin/saadc.rs rename to examples/nrf52840/src/bin/saadc.rs diff --git a/examples/nrf/src/bin/saadc_continuous.rs b/examples/nrf52840/src/bin/saadc_continuous.rs similarity index 95% rename from examples/nrf/src/bin/saadc_continuous.rs rename to examples/nrf52840/src/bin/saadc_continuous.rs index bb50ac65e..2551d15fd 100644 --- a/examples/nrf/src/bin/saadc_continuous.rs +++ b/examples/nrf52840/src/bin/saadc_continuous.rs @@ -5,7 +5,7 @@ use defmt::info; use embassy_executor::Spawner; use embassy_nrf::interrupt; -use embassy_nrf::saadc::{ChannelConfig, Config, Saadc, SamplerState}; +use embassy_nrf::saadc::{CallbackResult, ChannelConfig, Config, Saadc}; use embassy_nrf::timer::Frequency; use embassy_time::Duration; use {defmt_rtt as _, panic_probe as _}; @@ -61,7 +61,7 @@ async fn main(_p: Spawner) { c = 0; a = 0; } - SamplerState::Sampled + CallbackResult::Continue }, ) .await; diff --git a/examples/nrf/src/bin/self_spawn.rs b/examples/nrf52840/src/bin/self_spawn.rs similarity index 100% rename from examples/nrf/src/bin/self_spawn.rs rename to examples/nrf52840/src/bin/self_spawn.rs diff --git a/examples/nrf/src/bin/self_spawn_current_executor.rs b/examples/nrf52840/src/bin/self_spawn_current_executor.rs similarity index 100% rename from examples/nrf/src/bin/self_spawn_current_executor.rs rename to examples/nrf52840/src/bin/self_spawn_current_executor.rs diff --git a/examples/nrf/src/bin/spim.rs b/examples/nrf52840/src/bin/spim.rs similarity index 100% rename from examples/nrf/src/bin/spim.rs rename to examples/nrf52840/src/bin/spim.rs diff --git a/examples/nrf52840/src/bin/spis.rs b/examples/nrf52840/src/bin/spis.rs new file mode 100644 index 000000000..fe3b0c53d --- /dev/null +++ b/examples/nrf52840/src/bin/spis.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::interrupt; +use embassy_nrf::spis::{Config, Spis}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Running!"); + + let irq = interrupt::take!(SPIM2_SPIS2_SPI2); + let mut spis = Spis::new(p.SPI2, irq, p.P0_31, p.P0_29, p.P0_28, p.P0_30, Config::default()); + + loop { + let mut rx_buf = [0_u8; 64]; + let tx_buf = [1_u8, 2, 3, 4, 5, 6, 7, 8]; + if let Ok((n_rx, n_tx)) = spis.transfer(&mut rx_buf, &tx_buf).await { + info!("RX: {:?}", rx_buf[..n_rx]); + info!("TX: {:?}", tx_buf[..n_tx]); + } + } +} diff --git a/examples/nrf/src/bin/temp.rs b/examples/nrf52840/src/bin/temp.rs similarity index 100% rename from examples/nrf/src/bin/temp.rs rename to examples/nrf52840/src/bin/temp.rs diff --git a/examples/nrf/src/bin/timer.rs b/examples/nrf52840/src/bin/timer.rs similarity index 100% rename from examples/nrf/src/bin/timer.rs rename to examples/nrf52840/src/bin/timer.rs diff --git a/examples/nrf/src/bin/twim.rs b/examples/nrf52840/src/bin/twim.rs similarity index 100% rename from examples/nrf/src/bin/twim.rs rename to examples/nrf52840/src/bin/twim.rs diff --git a/examples/nrf/src/bin/twim_lowpower.rs b/examples/nrf52840/src/bin/twim_lowpower.rs similarity index 100% rename from examples/nrf/src/bin/twim_lowpower.rs rename to examples/nrf52840/src/bin/twim_lowpower.rs diff --git a/examples/nrf52840/src/bin/twis.rs b/examples/nrf52840/src/bin/twis.rs new file mode 100644 index 000000000..54cba9494 --- /dev/null +++ b/examples/nrf52840/src/bin/twis.rs @@ -0,0 +1,46 @@ +//! TWIS example + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::interrupt; +use embassy_nrf::twis::{self, Command, Twis}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); + let mut config = twis::Config::default(); + // Set i2c address + config.address0 = 0x55; + let mut i2c = Twis::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config); + + info!("Listening..."); + loop { + let response = [1, 2, 3, 4, 5, 6, 7, 8]; + // This buffer is used if the i2c master performs a Write or WriteRead + let mut buf = [0u8; 16]; + match i2c.listen(&mut buf).await { + Ok(Command::Read) => { + info!("Got READ command. Respond with data:\n{:?}\n", response); + if let Err(e) = i2c.respond_to_read(&response).await { + error!("{:?}", e); + } + } + Ok(Command::Write(n)) => info!("Got WRITE command with data:\n{:?}\n", buf[..n]), + Ok(Command::WriteRead(n)) => { + info!("Got WRITE/READ command with data:\n{:?}", buf[..n]); + info!("Respond with data:\n{:?}\n", response); + if let Err(e) = i2c.respond_to_read(&response).await { + error!("{:?}", e); + } + } + Err(e) => error!("{:?}", e), + } + } +} diff --git a/examples/nrf/src/bin/uart.rs b/examples/nrf52840/src/bin/uart.rs similarity index 100% rename from examples/nrf/src/bin/uart.rs rename to examples/nrf52840/src/bin/uart.rs diff --git a/examples/nrf/src/bin/uart_idle.rs b/examples/nrf52840/src/bin/uart_idle.rs similarity index 74% rename from examples/nrf/src/bin/uart_idle.rs rename to examples/nrf52840/src/bin/uart_idle.rs index 09ec624c0..6af4f7097 100644 --- a/examples/nrf/src/bin/uart_idle.rs +++ b/examples/nrf52840/src/bin/uart_idle.rs @@ -15,7 +15,8 @@ async fn main(_spawner: Spawner) { config.baudrate = uarte::Baudrate::BAUD115200; let irq = interrupt::take!(UARTE0_UART0); - let mut uart = uarte::UarteWithIdle::new(p.UARTE0, p.TIMER0, p.PPI_CH0, p.PPI_CH1, irq, p.P0_08, p.P0_06, config); + let uart = uarte::Uarte::new(p.UARTE0, irq, p.P0_08, p.P0_06, config); + let (mut tx, mut rx) = uart.split_with_idle(p.TIMER0, p.PPI_CH0, p.PPI_CH1); info!("uarte initialized!"); @@ -23,12 +24,12 @@ async fn main(_spawner: Spawner) { let mut buf = [0; 8]; buf.copy_from_slice(b"Hello!\r\n"); - unwrap!(uart.write(&buf).await); + unwrap!(tx.write(&buf).await); info!("wrote hello in uart!"); loop { info!("reading..."); - let n = unwrap!(uart.read_until_idle(&mut buf).await); + let n = unwrap!(rx.read_until_idle(&mut buf).await); info!("got {} bytes", n); } } diff --git a/examples/nrf/src/bin/uart_split.rs b/examples/nrf52840/src/bin/uart_split.rs similarity index 100% rename from examples/nrf/src/bin/uart_split.rs rename to examples/nrf52840/src/bin/uart_split.rs diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs new file mode 100644 index 000000000..430468adf --- /dev/null +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -0,0 +1,186 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::mem; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_nrf::rng::Rng; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; +use embassy_nrf::{interrupt, pac, peripherals}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, Config, UsbDevice}; +use embedded_io::asynch::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +const MTU: usize = 1514; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { + device.run().await +} + +#[embassy_executor::task] +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[inline(never)] +pub fn test_function() -> (usize, u32, [u32; 2]) { + let mut array = [3; 2]; + + let mut index = 0; + let mut result = 0; + + for x in [1, 2] { + if x == 1 { + array[1] = 99; + } else { + index = if x == 2 { 1 } else { 0 }; + + // grabs value from array[0], not array[1] + result = array[index]; + } + } + + (index, result, array) +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("{:?}", test_function()); + + let p = embassy_nrf::init(Default::default()); + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let power_irq = interrupt::take!(POWER_CLOCK); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Ethernet example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for Windows support. + config.composite_with_iads = true; + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], + ); + + // Our MAC addr. + let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; + // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. + let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; + + // Create classes on the builder. + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::Dhcp(Default::default()); + //let config = embassy_net::Config::Static(embassy_net::StaticConfig { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let mut rng = Rng::new(p.RNG, interrupt::take!(RNG)); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf52840/src/bin/usb_hid_keyboard.rs similarity index 91% rename from examples/nrf/src/bin/usb_hid_keyboard.rs rename to examples/nrf52840/src/bin/usb_hid_keyboard.rs index 76e198719..3d8a114cd 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf52840/src/bin/usb_hid_keyboard.rs @@ -10,13 +10,13 @@ use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_futures::select::{select, Either}; use embassy_nrf::gpio::{Input, Pin, Pull}; -use embassy_nrf::usb::{Driver, PowerUsb}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::signal::Signal; use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; -use embassy_usb::{Builder, Config, DeviceStateHandler}; +use embassy_usb::{Builder, Config, Handler}; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; @@ -34,7 +34,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -52,7 +52,7 @@ async fn main(_spawner: Spawner) { let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 64]; let request_handler = MyRequestHandler {}; - let device_state_handler = MyDeviceStateHandler::new(); + let mut device_handler = MyDeviceHandler::new(); let mut state = State::new(); @@ -63,9 +63,10 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - Some(&device_state_handler), ); + builder.handler(&mut device_handler); + // Create classes on the builder. let config = embassy_usb::class::hid::Config { report_descriptor: KeyboardReport::desc(), @@ -164,20 +165,20 @@ impl RequestHandler for MyRequestHandler { } } -struct MyDeviceStateHandler { +struct MyDeviceHandler { configured: AtomicBool, } -impl MyDeviceStateHandler { +impl MyDeviceHandler { fn new() -> Self { - MyDeviceStateHandler { + MyDeviceHandler { configured: AtomicBool::new(false), } } } -impl DeviceStateHandler for MyDeviceStateHandler { - fn enabled(&self, enabled: bool) { +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { self.configured.store(false, Ordering::Relaxed); SUSPENDED.store(false, Ordering::Release); if enabled { @@ -187,17 +188,17 @@ impl DeviceStateHandler for MyDeviceStateHandler { } } - fn reset(&self) { + fn reset(&mut self) { self.configured.store(false, Ordering::Relaxed); info!("Bus reset, the Vbus current limit is 100mA"); } - fn addressed(&self, addr: u8) { + fn addressed(&mut self, addr: u8) { self.configured.store(false, Ordering::Relaxed); info!("USB address set to: {}", addr); } - fn configured(&self, configured: bool) { + fn configured(&mut self, configured: bool) { self.configured.store(configured, Ordering::Relaxed); if configured { info!("Device configured, it may now draw up to the configured current limit from Vbus.") @@ -206,7 +207,7 @@ impl DeviceStateHandler for MyDeviceStateHandler { } } - fn suspended(&self, suspended: bool) { + fn suspended(&mut self, suspended: bool) { if suspended { info!("Device suspended, the Vbus current limit is 500µA (or 2.5mA for high-power devices with remote wakeup enabled)."); SUSPENDED.store(true, Ordering::Release); diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf52840/src/bin/usb_hid_mouse.rs similarity index 96% rename from examples/nrf/src/bin/usb_hid_mouse.rs rename to examples/nrf52840/src/bin/usb_hid_mouse.rs index 4916a38d4..d7c9d55b7 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf52840/src/bin/usb_hid_mouse.rs @@ -7,7 +7,7 @@ use core::mem; use defmt::*; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_nrf::usb::{Driver, PowerUsb}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_time::{Duration, Timer}; use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; @@ -28,7 +28,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -55,7 +55,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf52840/src/bin/usb_serial.rs similarity index 93% rename from examples/nrf/src/bin/usb_serial.rs rename to examples/nrf52840/src/bin/usb_serial.rs index 7c9c4184b..102d7ea60 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf52840/src/bin/usb_serial.rs @@ -7,7 +7,7 @@ use core::mem; use defmt::{info, panic}; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; @@ -26,7 +26,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -59,7 +59,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. @@ -97,7 +96,7 @@ impl From for Disconnected { } } -async fn echo<'d, T: Instance + 'd, P: UsbSupply + 'd>( +async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, ) -> Result<(), Disconnected> { let mut buf = [0; 64]; diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf52840/src/bin/usb_serial_multitask.rs similarity index 94% rename from examples/nrf/src/bin/usb_serial_multitask.rs rename to examples/nrf52840/src/bin/usb_serial_multitask.rs index 93efc2fe6..558d4ba60 100644 --- a/examples/nrf/src/bin/usb_serial_multitask.rs +++ b/examples/nrf52840/src/bin/usb_serial_multitask.rs @@ -6,7 +6,7 @@ use core::mem; use defmt::{info, panic, unwrap}; use embassy_executor::Spawner; -use embassy_nrf::usb::{Driver, PowerUsb}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; use embassy_nrf::{interrupt, pac, peripherals}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; @@ -14,7 +14,7 @@ use embassy_usb::{Builder, Config, UsbDevice}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -type MyDriver = Driver<'static, peripherals::USBD, PowerUsb>; +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) { @@ -42,7 +42,7 @@ async fn main(spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -83,7 +83,6 @@ async fn main(spawner: Spawner) { &mut res.config_descriptor, &mut res.bos_descriptor, &mut res.control_buf, - None, ); // Create classes on the builder. diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs new file mode 100644 index 000000000..6561fc3b4 --- /dev/null +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -0,0 +1,130 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::mem; + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect}; +use embassy_nrf::{interrupt, pac}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::types::InterfaceNumber; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let power_irq = interrupt::take!(POWER_CLOCK); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.msos_descriptor(windows_version::WIN8_1, 2); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Since we want to create MS OS feature descriptors that apply to a function that has already been added to the + // builder, need to get the MsOsDescriptorWriter from the builder and manually add those descriptors. + // Inside a class constructor, you would just need to call `FunctionBuilder::msos_feature` instead. + let msos_writer = builder.msos_writer(); + msos_writer.configuration(0); + msos_writer.function(InterfaceNumber(0)); + msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( + class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/nrf/src/bin/wdt.rs b/examples/nrf52840/src/bin/wdt.rs similarity index 100% rename from examples/nrf/src/bin/wdt.rs rename to examples/nrf52840/src/bin/wdt.rs diff --git a/examples/nrf5340/.cargo/config.toml b/examples/nrf5340/.cargo/config.toml new file mode 100644 index 000000000..ff0879c8c --- /dev/null +++ b/examples/nrf5340/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF5340_xxAA with your chip as listed in `probe-run --list-chips` +runner = "probe-run --chip nRF5340_xxAA" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml new file mode 100644 index 000000000..eed493012 --- /dev/null +++ b/examples/nrf5340/Cargo.toml @@ -0,0 +1,64 @@ +[package] +edition = "2021" +name = "embassy-nrf5340-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[features] +default = ["nightly"] +nightly = [ + "embassy-executor/nightly", + "embassy-nrf/nightly", + "embassy-net/nightly", + "embassy-nrf/unstable-traits", + "embassy-usb", + "embedded-io/async", + "embassy-net", +] + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = [ + "defmt", +] } +embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = [ + "defmt", + "integrated-timers", +] } +embassy-time = { version = "0.1.0", 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-net = { version = "0.1.0", path = "../../embassy-net", features = [ + "defmt", + "tcp", + "dhcpv4", + "medium-ethernet", +], optional = true } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = [ + "defmt", +], optional = true } +embedded-io = "0.4.0" + + +defmt = "0.3" +defmt-rtt = "0.4" + +static_cell = "1.0" +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = [ + "async-await", +] } +rand = { version = "0.8.4", default-features = false } +embedded-storage = "0.3.0" +usbd-hid = "0.6.0" +serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf5340/build.rs b/examples/nrf5340/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/nrf5340/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/nrf5340/memory.x b/examples/nrf5340/memory.x new file mode 100644 index 000000000..a122dc24a --- /dev/null +++ b/examples/nrf5340/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* These values correspond to the NRF5340 */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/examples/nrf5340/src/bin/blinky.rs b/examples/nrf5340/src/bin/blinky.rs new file mode 100644 index 000000000..3422cedf0 --- /dev/null +++ b/examples/nrf5340/src/bin/blinky.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_28, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/nrf5340/src/bin/gpiote_channel.rs b/examples/nrf5340/src/bin/gpiote_channel.rs new file mode 100644 index 000000000..ceab1194a --- /dev/null +++ b/examples/nrf5340/src/bin/gpiote_channel.rs @@ -0,0 +1,66 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Pull}; +use embassy_nrf::gpiote::{InputChannel, InputChannelPolarity}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Starting!"); + + let ch1 = InputChannel::new( + p.GPIOTE_CH0, + Input::new(p.P0_23, Pull::Up), + InputChannelPolarity::HiToLo, + ); + let ch2 = InputChannel::new( + p.GPIOTE_CH1, + Input::new(p.P0_24, Pull::Up), + InputChannelPolarity::LoToHi, + ); + let ch3 = InputChannel::new( + p.GPIOTE_CH2, + Input::new(p.P0_08, Pull::Up), + InputChannelPolarity::Toggle, + ); + let ch4 = InputChannel::new( + p.GPIOTE_CH3, + Input::new(p.P0_09, Pull::Up), + InputChannelPolarity::Toggle, + ); + + let button1 = async { + loop { + ch1.wait().await; + info!("Button 1 pressed") + } + }; + + let button2 = async { + loop { + ch2.wait().await; + info!("Button 2 released") + } + }; + + let button3 = async { + loop { + ch3.wait().await; + info!("Button 3 toggled") + } + }; + + let button4 = async { + loop { + ch4.wait().await; + info!("Button 4 toggled") + } + }; + + futures::join!(button1, button2, button3, button4); +} diff --git a/examples/nrf5340/src/bin/uart.rs b/examples/nrf5340/src/bin/uart.rs new file mode 100644 index 000000000..0f2b7b1e3 --- /dev/null +++ b/examples/nrf5340/src/bin/uart.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::{interrupt, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD115200; + + let irq = interrupt::take!(SERIAL0); + let mut uart = uarte::Uarte::new(p.UARTETWISPI0, irq, p.P1_00, p.P1_01, config); + + info!("uarte initialized!"); + + // Message must be in SRAM + let mut buf = [0; 8]; + buf.copy_from_slice(b"Hello!\r\n"); + + unwrap!(uart.write(&buf).await); + info!("wrote hello in uart!"); + + loop { + info!("reading..."); + unwrap!(uart.read(&mut buf).await); + info!("writing..."); + unwrap!(uart.write(&buf).await); + } +} diff --git a/examples/rp/.cargo/config.toml b/examples/rp/.cargo/config.toml index 3d6051389..d1c8c1c5a 100644 --- a/examples/rp/.cargo/config.toml +++ b/examples/rp/.cargo/config.toml @@ -5,4 +5,4 @@ runner = "probe-run --chip RP2040" target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "debug" diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index a5af8b2f0..f07684f29 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -9,15 +9,17 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } +embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio", "critical-section-impl"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6" } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } @@ -28,6 +30,13 @@ display-interface = "0.4.1" byte-slice-cast = { version = "1.2.0", default-features = false } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } -embedded-hal-async = { version = "0.1.0-alpha.1" } -embedded-io = { version = "0.3.0", features = ["async", "defmt"] } +embedded-hal-async = "0.2.0-alpha.0" +embedded-io = { version = "0.4.0", features = ["async", "defmt"] } +embedded-storage = { version = "0.3" } static_cell = "1.0.0" +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" + +[profile.release] +debug = true diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs new file mode 100644 index 000000000..4202fd394 --- /dev/null +++ b/examples/rp/src/bin/adc.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Config}; +use embassy_rp::interrupt; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let irq = interrupt::take!(ADC_IRQ_FIFO); + let mut adc = Adc::new(p.ADC, irq, Config::default()); + + let mut p26 = p.PIN_26; + let mut p27 = p.PIN_27; + let mut p28 = p.PIN_28; + + loop { + let level = adc.read(&mut p26).await; + info!("Pin 26 ADC: {}", level); + let level = adc.read(&mut p27).await; + info!("Pin 27 ADC: {}", level); + let level = adc.read(&mut p28).await; + info!("Pin 28 ADC: {}", level); + let temp = adc.read_temperature().await; + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after(Duration::from_secs(1)).await; + } +} + +fn convert_to_celsius(raw_temp: u16) -> f32 { + // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721 as f32 +} diff --git a/examples/rp/src/bin/flash.rs b/examples/rp/src/bin/flash.rs new file mode 100644 index 000000000..8d6b379f4 --- /dev/null +++ b/examples/rp/src/bin/flash.rs @@ -0,0 +1,89 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::flash::{ERASE_SIZE, FLASH_BASE}; +use embassy_rp::peripherals::FLASH; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +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(Duration::from_millis(10)).await; + + let mut flash = embassy_rp::flash::Flash::<_, FLASH_SIZE>::new(p.FLASH); + erase_write_sector(&mut flash, 0x00); + + multiwrite_bytes(&mut flash, ERASE_SIZE as u32); + + loop {} +} + +fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, FLASH_SIZE>, offset: u32) { + info!(">>>> [multiwrite_bytes]"); + let mut read_buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.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.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.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.write(ADDR_OFFSET + offset, &[0x01])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 1, &[0x02])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 2, &[0x03])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 3, &[0x04])); + + defmt::unwrap!(flash.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, FLASH_SIZE>, offset: u32) { + info!(">>>> [erase_write_sector]"); + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.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.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.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.write(ADDR_OFFSET + offset, &buf)); + + defmt::unwrap!(flash.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"); + } +} diff --git a/examples/rp/src/bin/i2c_async.rs b/examples/rp/src/bin/i2c_async.rs new file mode 100644 index 000000000..d1a2e3cd7 --- /dev/null +++ b/examples/rp/src/bin/i2c_async.rs @@ -0,0 +1,102 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::i2c::{self, Config}; +use embassy_rp::interrupt; +use embassy_time::{Duration, Timer}; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[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; + let irq = interrupt::take!(I2C1_IRQ); + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_async(p.I2C1, scl, sda, irq, 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(Duration::from_millis(100)).await; + } +} diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs new file mode 100644 index 000000000..376b2b61e --- /dev/null +++ b/examples/rp/src/bin/multicore.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Executor; +use embassy_executor::_export::StaticCell; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::PIN_25; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +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 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(Duration::from_millis(100)).await; + CHANNEL.send(LedState::Off).await; + Timer::after(Duration::from_millis(400)).await; + } +} + +#[embassy_executor::task] +async fn core1_task(mut led: Output<'static, PIN_25>) { + info!("Hello from core 1"); + loop { + match CHANNEL.recv().await { + LedState::On => led.set_high(), + LedState::Off => led.set_low(), + } + } +} diff --git a/examples/rp/src/bin/pio_async.rs b/examples/rp/src/bin/pio_async.rs new file mode 100644 index 000000000..45a8c73f7 --- /dev/null +++ b/examples/rp/src/bin/pio_async.rs @@ -0,0 +1,112 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::gpio::{AnyPin, Pin}; +use embassy_rp::pio::{Pio0, PioPeripherial, PioStateMachine, PioStateMachineInstance, ShiftDirection, Sm0, Sm1, Sm2}; +use embassy_rp::pio_instr_util; +use embassy_rp::relocate::RelocatedProgram; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn pio_task_sm0(mut sm: PioStateMachineInstance, pin: AnyPin) { + // 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 relocated = RelocatedProgram::new(&prg.program); + let out_pin = sm.make_pio_pin(pin); + let pio_pins = [&out_pin]; + sm.set_out_pins(&pio_pins); + sm.write_instr(relocated.origin() as usize, relocated.code()); + pio_instr_util::exec_jmp(&mut sm, relocated.origin()); + sm.set_clkdiv((125e6 / 20.0 / 2e2 * 256.0) as u32); + sm.set_set_range(0, 1); + let pio::Wrap { source, target } = relocated.wrap(); + sm.set_wrap(source, target); + + sm.set_autopull(true); + sm.set_out_shift_dir(ShiftDirection::Left); + + sm.set_enable(true); + + let mut v = 0x0f0caffa; + loop { + sm.wait_push(v).await; + v ^= 0xffff; + info!("Pushed {:032b} to FIFO", v); + } +} + +#[embassy_executor::task] +async fn pio_task_sm1(mut sm: PioStateMachineInstance) { + // 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 relocated = RelocatedProgram::new(&prg.program); + sm.write_instr(relocated.origin() as usize, relocated.code()); + pio_instr_util::exec_jmp(&mut sm, relocated.origin()); + sm.set_clkdiv((125e6 / 2e3 * 256.0) as u32); + sm.set_set_range(0, 0); + let pio::Wrap { source, target } = relocated.wrap(); + sm.set_wrap(source, target); + + sm.set_autopush(true); + sm.set_in_shift_dir(ShiftDirection::Right); + sm.set_enable(true); + loop { + let rx = sm.wait_pull().await; + info!("Pulled {:032b} from FIFO", rx); + } +} + +#[embassy_executor::task] +async fn pio_task_sm2(mut sm: PioStateMachineInstance) { + // 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 relocated = RelocatedProgram::new(&prg.program); + sm.write_instr(relocated.origin() as usize, relocated.code()); + + let pio::Wrap { source, target } = relocated.wrap(); + sm.set_wrap(source, target); + + pio_instr_util::exec_jmp(&mut sm, relocated.origin()); + sm.set_clkdiv((125e6 / 2e3 * 256.0) as u32); + sm.set_enable(true); + loop { + sm.wait_irq(3).await; + info!("IRQ trigged"); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + + let (_, sm0, sm1, sm2, ..) = pio.split(); + + spawner.spawn(pio_task_sm0(sm0, p.PIN_0.degrade())).unwrap(); + spawner.spawn(pio_task_sm1(sm1)).unwrap(); + spawner.spawn(pio_task_sm2(sm2)).unwrap(); +} diff --git a/examples/rp/src/bin/pio_dma.rs b/examples/rp/src/bin/pio_dma.rs new file mode 100644 index 000000000..b19ef4083 --- /dev/null +++ b/examples/rp/src/bin/pio_dma.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::pio::{PioPeripherial, PioStateMachine, ShiftDirection}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{pio_instr_util, Peripheral}; +use {defmt_rtt as _, panic_probe as _}; + +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 (_, mut sm, ..) = pio.split(); + + 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 relocated = RelocatedProgram::new(&prg.program); + sm.write_instr(relocated.origin() as usize, relocated.code()); + pio_instr_util::exec_jmp(&mut sm, relocated.origin()); + sm.set_clkdiv((125e6 / 10e3 * 256.0) as u32); + let pio::Wrap { source, target } = relocated.wrap(); + sm.set_wrap(source, target); + sm.set_autopull(true); + sm.set_autopush(true); + sm.set_pull_threshold(32); + sm.set_push_threshold(32); + sm.set_out_shift_dir(ShiftDirection::Right); + sm.set_in_shift_dir(ShiftDirection::Left); + + 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 { + join( + sm.dma_push(dma_out_ref.reborrow(), &dout), + sm.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/rp/src/bin/uart_buffered_split.rs b/examples/rp/src/bin/uart_buffered_split.rs new file mode 100644 index 000000000..a8a682274 --- /dev/null +++ b/examples/rp/src/bin/uart_buffered_split.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_executor::_export::StaticCell; +use embassy_rp::interrupt; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedUart, BufferedUartRx, Config}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[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); + + let irq = interrupt::take!(UART0_IRQ); + let tx_buf = &mut singleton!([0u8; 16])[..]; + let rx_buf = &mut singleton!([0u8; 16])[..]; + let uart = BufferedUart::new(uart, irq, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let (rx, mut tx) = 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(Duration::from_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/rp/src/bin/uart_unidir.rs b/examples/rp/src/bin/uart_unidir.rs new file mode 100644 index 000000000..f56e7009f --- /dev/null +++ b/examples/rp/src/bin/uart_unidir.rs @@ -0,0 +1,42 @@ +//! test TX-only and RX-only UARTs. You need to connect GPIO0 to GPIO5 for +//! this to work + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::peripherals::UART1; +use embassy_rp::uart::{Async, Config, UartRx, UartTx}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[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, 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(Duration::from_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/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 1057fe7fd..66a6ed4d0 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -2,18 +2,14 @@ #![no_main] #![feature(type_alias_impl_trait)] -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; - use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; +use embassy_net::{Stack, StackResources}; use embassy_rp::usb::Driver; use embassy_rp::{interrupt, peripherals}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; -use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, Config, UsbDevice}; use embedded_io::asynch::Write; use static_cell::StaticCell; @@ -25,56 +21,25 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } +const MTU: usize = 1514; + #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { device.run().await } #[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await } #[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { +async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } @@ -100,58 +65,34 @@ async fn main(spawner: Spawner) { config.device_sub_class = 0x02; config.device_protocol = 0x01; - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - // Create embassy-usb DeviceBuilder using the driver and config. let mut builder = Builder::new( driver, config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, - None, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], ); - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - // Our MAC addr. let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); // Build the builder. let usb = builder.build(); unwrap!(spawner.spawn(usb_task(usb))); - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::Dhcp(Default::default()); + //let config = embassy_net::Config::Static(embassy_net::StaticConfig { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), @@ -161,13 +102,7 @@ async fn main(spawner: Spawner) { let seed = 1234; // guaranteed random, chosen by a fair dice roll // Init network stack - let device = Device { mac_addr: our_mac_addr }; - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); unwrap!(spawner.spawn(net_task(stack))); @@ -214,50 +149,3 @@ async fn main(spawner: Spawner) { } } } - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} diff --git a/examples/rp/src/bin/usb_logger.rs b/examples/rp/src/bin/usb_logger.rs new file mode 100644 index 000000000..52417a02e --- /dev/null +++ b/examples/rp/src/bin/usb_logger.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_rp::interrupt; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::Driver; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let irq = interrupt::take!(USBCTRL_IRQ); + let driver = Driver::new(p.USB, irq); + spawner.spawn(logger_task(driver)).unwrap(); + + let mut counter = 0; + loop { + counter += 1; + log::info!("Tick {}", counter); + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs index b7d6493b4..a991082ee 100644 --- a/examples/rp/src/bin/usb_serial.rs +++ b/examples/rp/src/bin/usb_serial.rs @@ -53,7 +53,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/rp/src/bin/watchdog.rs b/examples/rp/src/bin/watchdog.rs new file mode 100644 index 000000000..ece5cfe38 --- /dev/null +++ b/examples/rp/src/bin/watchdog.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_rp::watchdog::*; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +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 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(Duration::from_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(Duration::from_millis(500)).await; + led.set_high(); + Timer::after(Duration::from_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(Duration::from_millis(100)).await; + led.set_high(); + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index b9bd1e718..8087df09a 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -8,15 +8,16 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["log"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["log", "std", "nightly", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["log", "std", "nightly"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dhcpv4", "pool-16"] } -embedded-io = { version = "0.3.0", features = ["async", "std", "futures"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dns", "dhcpv4", "unstable-traits", "proto-ipv6"] } +embassy-net-driver = { version = "0.1.0", path = "../../embassy-net-driver" } +embedded-io = { version = "0.4.0", features = ["async", "std", "futures"] } critical-section = { version = "1.1", features = ["std"] } async-io = "1.6.0" env_logger = "0.9.0" futures = { version = "0.3.17" } log = "0.4.14" -nix = "0.22.1" +nix = "0.26.2" libc = "0.2.101" clap = { version = "3.0.0-beta.5", features = ["derive"] } rand_core = { version = "0.6.3", features = ["std"] } diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 9b1450b72..451850d99 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -1,9 +1,11 @@ #![feature(type_alias_impl_trait)] +use std::default::Default; + use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{ConfigStrategy, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; use embedded_io::asynch::Write; use heapless::Vec; use log::*; @@ -48,13 +50,13 @@ async fn main_task(spawner: Spawner) { // Choose between dhcp or static ip let config = if opts.static_ip { - ConfigStrategy::Static(embassy_net::Config { + Config::Static(embassy_net::StaticConfig { address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), dns_servers: Vec::new(), gateway: Some(Ipv4Address::new(192, 168, 69, 1)), }) } else { - ConfigStrategy::Dhcp + Config::Dhcp(Default::default()) }; // Generate random seed @@ -63,12 +65,7 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); // Launch network task spawner.spawn(net_task(stack)).unwrap(); diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs new file mode 100644 index 000000000..e1cc45a38 --- /dev/null +++ b/examples/std/src/bin/net_dns.rs @@ -0,0 +1,98 @@ +#![feature(type_alias_impl_trait)] + +use std::default::Default; + +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::dns::DnsQueryType; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; + +#[path = "../tuntap.rs"] +mod tuntap; + +use crate::tuntap::TunTapDevice; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + STATIC_CELL.init_with(move || $val) + }}; +} + +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// TAP device name + #[clap(long, default_value = "tap0")] + tap: String, + /// use a static IP instead of DHCP + #[clap(long)] + static_ip: bool, +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Init network device + let device = TunTapDevice::new(&opts.tap).unwrap(); + + // Choose between dhcp or static ip + let config = if opts.static_ip { + Config::Static(embassy_net::StaticConfig { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 1), 24), + dns_servers: Vec::from_slice(&[Ipv4Address::new(8, 8, 4, 4).into(), Ipv4Address::new(8, 8, 8, 8).into()]) + .unwrap(), + gateway: Some(Ipv4Address::new(192, 168, 69, 100)), + }) + } else { + Config::Dhcp(Default::default()) + }; + + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack: &Stack<_> = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + + // Launch network task + spawner.spawn(net_task(stack)).unwrap(); + + let host = "example.com"; + info!("querying host {:?}...", host); + match stack.dns_query(host, DnsQueryType::A).await { + Ok(r) => { + info!("query response: {:?}", r); + } + Err(e) => { + warn!("query error: {:?}", e); + } + }; +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} diff --git a/examples/std/src/bin/net_udp.rs b/examples/std/src/bin/net_udp.rs index 392a97f0d..f1923f180 100644 --- a/examples/std/src/bin/net_udp.rs +++ b/examples/std/src/bin/net_udp.rs @@ -3,7 +3,7 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::udp::UdpSocket; -use embassy_net::{ConfigStrategy, Ipv4Address, Ipv4Cidr, PacketMetadata, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, PacketMetadata, Stack, StackResources}; use heapless::Vec; use log::*; use rand_core::{OsRng, RngCore}; @@ -47,13 +47,13 @@ async fn main_task(spawner: Spawner) { // Choose between dhcp or static ip let config = if opts.static_ip { - ConfigStrategy::Static(embassy_net::Config { + Config::Static(embassy_net::StaticConfig { address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), dns_servers: Vec::new(), gateway: Some(Ipv4Address::new(192, 168, 69, 1)), }) } else { - ConfigStrategy::Dhcp + Config::Dhcp(Default::default()) }; // Generate random seed @@ -62,12 +62,7 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); // Launch network task spawner.spawn(net_task(stack)).unwrap(); diff --git a/examples/std/src/tuntap.rs b/examples/std/src/tuntap.rs index a0cace7f7..d918a2e62 100644 --- a/examples/std/src/tuntap.rs +++ b/examples/std/src/tuntap.rs @@ -1,8 +1,10 @@ use std::io; use std::io::{Read, Write}; use std::os::unix::io::{AsRawFd, RawFd}; +use std::task::Context; use async_io::Async; +use embassy_net_driver::{self, Capabilities, Driver, LinkState}; use log::*; pub const SIOCGIFMTU: libc::c_ulong = 0x8921; @@ -125,54 +127,35 @@ impl io::Write for TunTap { pub struct TunTapDevice { device: Async, - waker: Option, } impl TunTapDevice { pub fn new(name: &str) -> io::Result { Ok(Self { device: Async::new(TunTap::new(name)?)?, - waker: None, }) } } -use core::task::Waker; -use std::task::Context; +impl Driver for TunTapDevice { + type RxToken<'a> = RxToken where Self: 'a; + type TxToken<'a> = TxToken<'a> where Self: 'a; -use embassy_net::{Device, DeviceCapabilities, LinkState, Packet, PacketBox, PacketBoxExt, PacketBuf}; - -impl Device for TunTapDevice { - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - // todo handle WouldBlock - match self.device.get_mut().write(&pkt) { - Ok(_) => {} - Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - info!("transmit WouldBlock"); - } - Err(e) => panic!("transmit error: {:?}", e), - } - } - - fn receive(&mut self) -> Option { - let mut pkt = PacketBox::new(Packet::new()).unwrap(); + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut buf = vec![0; self.device.get_ref().mtu]; loop { - match self.device.get_mut().read(&mut pkt[..]) { + match self.device.get_mut().read(&mut buf) { Ok(n) => { - return Some(pkt.slice(0..n)); + buf.truncate(n); + return Some(( + RxToken { buffer: buf }, + TxToken { + device: &mut self.device, + }, + )); } Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - let ready = if let Some(w) = self.waker.as_ref() { - let mut cx = Context::from_waker(w); - self.device.poll_readable(&mut cx).is_ready() - } else { - false - }; - if !ready { + if !self.device.poll_readable(cx).is_ready() { return None; } } @@ -181,37 +164,19 @@ impl Device for TunTapDevice { } } - fn register_waker(&mut self, w: &Waker) { - match self.waker { - // Optimization: If both the old and new Wakers wake the same task, we can simply - // keep the old waker, skipping the clone. (In most executor implementations, - // cloning a waker is somewhat expensive, comparable to cloning an Arc). - Some(ref w2) if (w2.will_wake(w)) => {} - _ => { - // clone the new waker and store it - if let Some(old_waker) = core::mem::replace(&mut self.waker, Some(w.clone())) { - // We had a waker registered for another task. Wake it, so the other task can - // reregister itself if it's still interested. - // - // If two tasks are waiting on the same thing concurrently, this will cause them - // to wake each other in a loop fighting over this WakerRegistration. This wastes - // CPU but things will still work. - // - // If the user wants to have two tasks waiting on the same thing they should use - // a more appropriate primitive that can store multiple wakers. - old_waker.wake() - } - } - } + fn transmit(&mut self, _cx: &mut Context) -> Option> { + Some(TxToken { + device: &mut self.device, + }) } - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); caps.max_transmission_unit = self.device.get_ref().mtu; caps } - fn link_state(&mut self) -> LinkState { + fn link_state(&mut self, _cx: &mut Context) -> LinkState { LinkState::Up } @@ -219,3 +184,41 @@ impl Device for TunTapDevice { [0x02, 0x03, 0x04, 0x05, 0x06, 0x07] } } + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl embassy_net_driver::RxToken for RxToken { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(&mut self.buffer) + } +} + +#[doc(hidden)] +pub struct TxToken<'a> { + device: &'a mut Async, +} + +impl<'a> embassy_net_driver::TxToken for TxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + + // todo handle WouldBlock with async + match self.device.get_mut().write(&buffer) { + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::WouldBlock => info!("transmit WouldBlock"), + Err(e) => panic!("transmit error: {:?}", e), + } + + result + } +} diff --git a/examples/stm32c0/.cargo/config.toml b/examples/stm32c0/.cargo/config.toml new file mode 100644 index 000000000..eb07f6190 --- /dev/null +++ b/examples/stm32c0/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32G071C8Rx with your chip as listed in `probe-run --list-chips` +runner = "probe-rs-cli run --speed 100 --chip STM32c031c6tx" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml new file mode 100644 index 000000000..0095a680c --- /dev/null +++ b/examples/stm32c0/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-stm32c0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } +embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = ["async-await"] } +heapless = { version = "0.7.5", default-features = false } diff --git a/examples/stm32c0/build.rs b/examples/stm32c0/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32c0/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32c0/src/bin/blinky.rs b/examples/stm32c0/src/bin/blinky.rs new file mode 100644 index 000000000..8a65b0692 --- /dev/null +++ b/examples/stm32c0/src/bin/blinky.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut led = Output::new(p.PA5, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + + info!("low"); + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/stm32c0/src/bin/button.rs b/examples/stm32c0/src/bin/button.rs new file mode 100644 index 000000000..72a3f5cbf --- /dev/null +++ b/examples/stm32c0/src/bin/button.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let button = Input::new(p.PC13, Pull::Up); + + loop { + if button.is_high() { + info!("high"); + } else { + info!("low"); + } + } +} diff --git a/examples/stm32c0/src/bin/button_exti.rs b/examples/stm32c0/src/bin/button_exti.rs new file mode 100644 index 000000000..ef32d4c4a --- /dev/null +++ b/examples/stm32c0/src/bin/button_exti.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let button = Input::new(p.PC13, Pull::Up); + let mut button = ExtiInput::new(button, p.EXTI13); + + info!("Press the USER button..."); + + loop { + button.wait_for_falling_edge().await; + info!("Pressed!"); + button.wait_for_rising_edge().await; + info!("Released!"); + } +} diff --git a/examples/stm32f0/.cargo/config.toml b/examples/stm32f0/.cargo/config.toml index d1b1cd0bf..16abc29bc 100644 --- a/examples/stm32f0/.cargo/config.toml +++ b/examples/stm32f0/.cargo/config.toml @@ -1,5 +1,5 @@ [target.thumbv6m-none-eabi] -runner = 'probe-run --chip STM32F030F4Px' +runner = 'probe-run --chip STM32F091RCTX' [build] target = "thumbv6m-none-eabi" diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index a56c546ee..d4afbb8f8 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -10,10 +10,10 @@ license = "MIT OR Apache-2.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" panic-probe = "0.3" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "memory-x", "stm32f030f4", "time-driver-any"] } - +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "memory-x", "stm32f091rc", "time-driver-any", "exti"] } +static_cell = "1.0" diff --git a/examples/stm32f0/src/bin/blinky.rs b/examples/stm32f0/src/bin/blinky.rs new file mode 100644 index 000000000..9f923399c --- /dev/null +++ b/examples/stm32f0/src/bin/blinky.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +// main is itself an async function. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + //PA5 is the onboard LED on the Nucleo F091RC + let mut led = Output::new(p.PA5, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + + info!("low"); + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/stm32f0/src/bin/button_controlled_blink.rs b/examples/stm32f0/src/bin/button_controlled_blink.rs new file mode 100644 index 000000000..e1f223433 --- /dev/null +++ b/examples/stm32f0/src/bin/button_controlled_blink.rs @@ -0,0 +1,64 @@ +//! This example showcases how to create task + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{AnyPin, Input, Level, Output, Pin, Pull, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +static BLINK_MS: AtomicU32 = AtomicU32::new(0); + +#[embassy_executor::task] +async fn led_task(led: AnyPin) { + // Configure the LED pin as a push pull ouput and obtain handler. + // On the Nucleo F091RC theres an on-board LED connected to pin PA5. + let mut led = Output::new(led, Level::Low, Speed::Low); + + loop { + let del = BLINK_MS.load(Ordering::Relaxed); + info!("Value of del is {}", del); + Timer::after(Duration::from_millis(del.into())).await; + info!("LED toggling"); + led.toggle(); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + + // Configure the button pin and obtain handler. + // On the Nucleo F091RC there is a button connected to pin PC13. + let button = Input::new(p.PC13, Pull::None); + let mut button = ExtiInput::new(button, p.EXTI13); + + // Create and initialize a delay variable to manage delay loop + let mut del_var = 2000; + + // Blink duration value to global context + BLINK_MS.store(del_var, Ordering::Relaxed); + + // Spawn LED blinking task + spawner.spawn(led_task(p.PA5.degrade())).unwrap(); + + loop { + // Check if button got pressed + button.wait_for_rising_edge().await; + info!("rising_edge"); + del_var = del_var - 200; + // If updated delay value drops below 200 then reset it back to starting value + if del_var < 200 { + del_var = 2000; + } + // Updated delay value to global context + BLINK_MS.store(del_var, Ordering::Relaxed); + } +} diff --git a/examples/stm32f0/src/bin/button_exti.rs b/examples/stm32f0/src/bin/button_exti.rs new file mode 100644 index 000000000..40c0d5848 --- /dev/null +++ b/examples/stm32f0/src/bin/button_exti.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + // Configure the button pin and obtain handler. + // On the Nucleo F091RC there is a button connected to pin PC13. + let button = Input::new(p.PC13, Pull::Down); + let mut button = ExtiInput::new(button, p.EXTI13); + + info!("Press the USER button..."); + loop { + button.wait_for_falling_edge().await; + info!("Pressed!"); + button.wait_for_rising_edge().await; + info!("Released!"); + } +} diff --git a/examples/stm32f0/src/bin/priority.rs b/examples/stm32f0/src/bin/priority.rs new file mode 100644 index 000000000..7fed6a773 --- /dev/null +++ b/examples/stm32f0/src/bin/priority.rs @@ -0,0 +1,139 @@ +//! 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] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::executor::{Executor, InterruptExecutor}; +use embassy_stm32::interrupt; +use embassy_stm32::interrupt::InterruptExt; +use embassy_time::{Duration, Instant, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run_high() { + loop { + // info!(" [high] tick!"); + Timer::after(Duration::from_ticks(27374)).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 + cortex_m::asm::delay(8_000_000); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after(Duration::from_ticks(23421)).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 + cortex_m::asm::delay(16_000_000); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after(Duration::from_ticks(32983)).await; + } +} + +static EXECUTOR_HIGH: StaticCell> = StaticCell::new(); +static EXECUTOR_MED: StaticCell> = StaticCell::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + // Initialize and create handle for devicer peripherals + let _p = embassy_stm32::init(Default::default()); + + // High-priority executor: USART1, priority level 6 + let irq = interrupt::take!(USART1); + irq.set_priority(interrupt::Priority::P6); + let executor = EXECUTOR_HIGH.init(InterruptExecutor::new(irq)); + let spawner = executor.start(); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: USART2, priority level 7 + let irq = interrupt::take!(USART2); + irq.set_priority(interrupt::Priority::P7); + let executor = EXECUTOR_MED.init(InterruptExecutor::new(irq)); + let spawner = executor.start(); + 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/stm32f0/src/bin/wdg.rs b/examples/stm32f0/src/bin/wdg.rs new file mode 100644 index 000000000..80e76f901 --- /dev/null +++ b/examples/stm32f0/src/bin/wdg.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + // Configure the independent watchdog timer + let mut wdg = IndependentWatchdog::new(p.IWDG, 20_000_00); + + info!("Watchdog start"); + unsafe { wdg.unleash() }; + + loop { + Timer::after(Duration::from_secs(1)).await; + unsafe { wdg.pet() }; + } +} diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 6be131f30..53f369b3a 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -13,7 +13,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32f1/src/bin/adc.rs b/examples/stm32f1/src/bin/adc.rs index 2d6b4a0e9..ed59e2799 100644 --- a/examples/stm32f1/src/bin/adc.rs +++ b/examples/stm32f1/src/bin/adc.rs @@ -16,11 +16,19 @@ async fn main(_spawner: Spawner) { let mut adc = Adc::new(p.ADC1, &mut Delay); let mut pin = p.PB1; - let mut vref = adc.enable_vref(&mut Delay); - adc.calibrate(&mut vref); + let mut vrefint = adc.enable_vref(&mut Delay); + let vrefint_sample = adc.read(&mut vrefint); + let convert_to_millivolts = |sample| { + // From http://www.st.com/resource/en/datasheet/CD00161566.pdf + // 5.3.4 Embedded reference voltage + const VREFINT_MV: u32 = 1200; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + loop { let v = adc.read(&mut pin); - info!("--> {} - {} mV", v, adc.to_millivolts(v)); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); Timer::after(Duration::from_millis(100)).await; } } diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs index ad92cdeb2..07cad84ef 100644 --- a/examples/stm32f1/src/bin/usb_serial.rs +++ b/examples/stm32f1/src/bin/usb_serial.rs @@ -58,7 +58,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index f6adda2a3..afaf9a0c9 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -11,7 +11,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index 27188dd19..69ebef786 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -13,7 +13,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32f3/src/bin/usart_dma.rs b/examples/stm32f3/src/bin/usart_dma.rs index 3bc5a287f..47121acf1 100644 --- a/examples/stm32f3/src/bin/usart_dma.rs +++ b/examples/stm32f3/src/bin/usart_dma.rs @@ -7,6 +7,7 @@ use core::fmt::Write; use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; @@ -17,7 +18,8 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.USART1, p.PE1, p.PE0, p.DMA1_CH4, NoDma, config); + let irq = interrupt::take!(USART1); + let mut usart = Uart::new(p.USART1, p.PE1, p.PE0, irq, p.DMA1_CH4, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs index f6d27c860..5b4e0a91a 100644 --- a/examples/stm32f3/src/bin/usb_serial.rs +++ b/examples/stm32f3/src/bin/usb_serial.rs @@ -55,7 +55,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 6d4f09fba..e2b17bfcb 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -4,20 +4,21 @@ name = "embassy-stm32f4-examples" version = "0.1.0" license = "MIT OR Apache-2.0" - [dependencies] embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"], optional = true } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-io = "0.3.0" +embedded-io = "0.4.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } @@ -26,5 +27,9 @@ embedded-storage = "0.3.0" micromath = "2.0.0" static_cell = "1.0" -usb-device = "0.2" -usbd-serial = "0.1.1" +[[bin]] +name = "usb_ethernet" +required-features = ["embassy-net"] + +[profile.release] +debug = 2 diff --git a/examples/stm32f4/src/bin/adc.rs b/examples/stm32f4/src/bin/adc.rs index 1d030f7dc..1c9a0b35d 100644 --- a/examples/stm32f4/src/bin/adc.rs +++ b/examples/stm32f4/src/bin/adc.rs @@ -24,19 +24,44 @@ async fn main(_spawner: Spawner) { // Startup delay can be combined to the maximum of either delay.delay_us(Temperature::start_time_us().max(VrefInt::start_time_us())); + let vrefint_sample = adc.read_internal(&mut vrefint); + + let convert_to_millivolts = |sample| { + // From http://www.st.com/resource/en/datasheet/DM00071990.pdf + // 6.3.24 Reference voltage + const VREFINT_MV: u32 = 1210; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + let convert_to_celcius = |sample| { + // From http://www.st.com/resource/en/datasheet/DM00071990.pdf + // 6.3.22 Temperature sensor characteristics + const V25: i32 = 760; // mV + const AVG_SLOPE: f32 = 2.5; // mV/C + + let sample_mv = convert_to_millivolts(sample) as i32; + + (sample_mv - V25) as f32 / AVG_SLOPE + 25.0 + }; + + info!("VrefInt: {}", vrefint_sample); + const MAX_ADC_SAMPLE: u16 = (1 << 12) - 1; + info!("VCCA: {} mV", convert_to_millivolts(MAX_ADC_SAMPLE)); + loop { // Read pin let v = adc.read(&mut pin); - info!("PC1: {} ({} mV)", v, adc.to_millivolts(v)); + info!("PC1: {} ({} mV)", v, convert_to_millivolts(v)); // Read internal temperature let v = adc.read_internal(&mut temp); - let celcius = Temperature::to_celcius(adc.to_millivolts(v)); + let celcius = convert_to_celcius(v); info!("Internal temp: {} ({} C)", v, celcius); // Read internal voltage reference let v = adc.read_internal(&mut vrefint); - info!("VrefInt: {} ({} mV)", v, adc.to_millivolts(v)); + info!("VrefInt: {}", v); Timer::after(Duration::from_millis(100)).await; } diff --git a/examples/stm32f4/src/bin/i2c.rs b/examples/stm32f4/src/bin/i2c.rs new file mode 100644 index 000000000..6e51c211d --- /dev/null +++ b/examples/stm32f4/src/bin/i2c.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::i2c::{Error, I2c, TimeoutI2c}; +use embassy_stm32::interrupt; +use embassy_stm32::time::Hertz; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let irq = interrupt::take!(I2C2_EV); + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + irq, + NoDma, + NoDma, + Hertz(100_000), + Default::default(), + ); + + // I2C bus can freeze if SCL line is shorted or due to a broken device that clock stretches for too long. + // TimeoutI2c allows recovering from such errors by throwing `Error::Timeout` after a given delay. + let mut timeout_i2c = TimeoutI2c::new(&mut i2c, Duration::from_millis(1000)); + + let mut data = [0u8; 1]; + + match timeout_i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data) { + Ok(()) => info!("Whoami: {}", data[0]), + Err(Error::Timeout) => error!("Operation timed out"), + Err(e) => error!("I2c Error: {:?}", e), + } +} diff --git a/examples/stm32f4/src/bin/usart.rs b/examples/stm32f4/src/bin/usart.rs index 90ad882b8..8f41bb6c4 100644 --- a/examples/stm32f4/src/bin/usart.rs +++ b/examples/stm32f4/src/bin/usart.rs @@ -5,6 +5,7 @@ use cortex_m_rt::entry; use defmt::*; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use {defmt_rtt as _, panic_probe as _}; @@ -15,7 +16,8 @@ fn main() -> ! { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, NoDma, NoDma, config); + let irq = interrupt::take!(USART3); + let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, irq, NoDma, NoDma, config); unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); info!("wrote Hello, starting echo"); diff --git a/examples/stm32f4/src/bin/usart_buffered.rs b/examples/stm32f4/src/bin/usart_buffered.rs index 7bcecbd26..dd171fe13 100644 --- a/examples/stm32f4/src/bin/usart_buffered.rs +++ b/examples/stm32f4/src/bin/usart_buffered.rs @@ -4,9 +4,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dma::NoDma; use embassy_stm32::interrupt; -use embassy_stm32::usart::{BufferedUart, Config, State, Uart}; +use embassy_stm32::usart::{BufferedUart, Config, State}; use embedded_io::asynch::BufRead; use {defmt_rtt as _, panic_probe as _}; @@ -16,13 +15,21 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let config = Config::default(); - let usart = Uart::new(p.USART3, p.PD9, p.PD8, NoDma, NoDma, config); let mut state = State::new(); let irq = interrupt::take!(USART3); let mut tx_buf = [0u8; 32]; let mut rx_buf = [0u8; 32]; - let mut buf_usart = BufferedUart::new(&mut state, usart, irq, &mut tx_buf, &mut rx_buf); + let mut buf_usart = BufferedUart::new( + &mut state, + p.USART3, + p.PD9, + p.PD8, + irq, + &mut tx_buf, + &mut rx_buf, + config, + ); loop { let buf = buf_usart.fill_buf().await.unwrap(); diff --git a/examples/stm32f4/src/bin/usart_dma.rs b/examples/stm32f4/src/bin/usart_dma.rs index bb41b8b4f..78baeaa0d 100644 --- a/examples/stm32f4/src/bin/usart_dma.rs +++ b/examples/stm32f4/src/bin/usart_dma.rs @@ -7,6 +7,7 @@ use core::fmt::Write; use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; @@ -17,7 +18,8 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, p.DMA1_CH3, NoDma, config); + let irq = interrupt::take!(USART3); + let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, irq, p.DMA1_CH3, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs new file mode 100644 index 000000000..4a16aac07 --- /dev/null +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -0,0 +1,168 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::Driver; +use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, UsbDevice}; +use embedded_io::asynch::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type UsbDriver = Driver<'static, embassy_stm32::peripherals::USB_OTG_FS>; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +const MTU: usize = 1514; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, UsbDriver>) -> ! { + device.run().await +} + +#[embassy_executor::task] +async fn usb_ncm_task(class: Runner<'static, UsbDriver, MTU>) -> ! { + class.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.pll48 = true; + config.rcc.sys_ck = Some(mhz(48)); + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let irq = interrupt::take!(OTG_FS); + let ep_out_buffer = &mut singleton!([0; 256])[..]; + let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, ep_out_buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Ethernet example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for Windows support. + config.composite_with_iads = true; + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], + ); + + // Our MAC addr. + let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; + // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. + let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; + + // Create classes on the builder. + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::ConfigStrategy::Dhcp; + //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let mut rng = Rng::new(p.RNG); + let mut seed = [0; 8]; + unwrap!(rng.async_fill_bytes(&mut seed).await); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + config, + singleton!(StackResources::<1, 2, 8>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs new file mode 100644 index 000000000..baabc1a2d --- /dev/null +++ b/examples/stm32f4/src/bin/usb_serial.rs @@ -0,0 +1,105 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.pll48 = true; + config.rcc.sys_ck = Some(mhz(48)); + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let irq = interrupt::take!(OTG_FS); + let mut ep_out_buffer = [0u8; 256]; + let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index dad92c0fc..ea4cbd808 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -8,12 +8,13 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "net", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } -embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } -embedded-io = { version = "0.3.0", features = ["async"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } +embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32f7/src/bin/adc.rs b/examples/stm32f7/src/bin/adc.rs index 80fad8c41..70b3b2a75 100644 --- a/examples/stm32f7/src/bin/adc.rs +++ b/examples/stm32f7/src/bin/adc.rs @@ -16,9 +16,19 @@ async fn main(_spawner: Spawner) { let mut adc = Adc::new(p.ADC1, &mut Delay); let mut pin = p.PA3; + let mut vrefint = adc.enable_vrefint(); + let vrefint_sample = adc.read_internal(&mut vrefint); + let convert_to_millivolts = |sample| { + // From http://www.st.com/resource/en/datasheet/DM00273119.pdf + // 6.3.27 Reference voltage + const VREFINT_MV: u32 = 1210; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + loop { let v = adc.read(&mut pin); - info!("--> {} - {} mV", v, adc.to_millivolts(v)); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); Timer::after(Duration::from_millis(100)).await; } } diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 5202edf62..571a6c1b9 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs @@ -7,7 +7,7 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; @@ -22,11 +22,12 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -50,40 +51,33 @@ async fn main(spawner: Spawner) -> ! { let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + singleton!(PacketQueue::<16, 16>::new()), + p.ETH, + eth_int, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI, + mac_addr, + 0, + ); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::Dhcp(Default::default()); + //let config = embassy_net::Config::Static(embassy_net::StaticConfig { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); // Init network stack - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); // Launch network task unwrap!(spawner.spawn(net_task(&stack))); @@ -91,8 +85,8 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); // Then we can use it! - let mut rx_buffer = [0; 1024]; - let mut tx_buffer = [0; 1024]; + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; loop { let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); @@ -107,8 +101,9 @@ async fn main(spawner: Spawner) -> ! { continue; } info!("connected!"); + let buf = [0; 1024]; loop { - let r = socket.write_all(b"Hello\n").await; + let r = socket.write_all(&buf).await; if let Err(e) = r { info!("write error: {:?}", e); return; diff --git a/examples/stm32f7/src/bin/usart_dma.rs b/examples/stm32f7/src/bin/usart_dma.rs index 07270479c..4827c52ae 100644 --- a/examples/stm32f7/src/bin/usart_dma.rs +++ b/examples/stm32f7/src/bin/usart_dma.rs @@ -7,6 +7,7 @@ use core::fmt::Write; use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; @@ -15,7 +16,8 @@ use {defmt_rtt as _, panic_probe as _}; async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PA8, p.PA15, p.DMA1_CH1, NoDma, config); + let irq = interrupt::take!(UART7); + let mut usart = Uart::new(p.UART7, p.PA8, p.PA15, irq, p.DMA1_CH1, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs new file mode 100644 index 000000000..5fd9d2ec9 --- /dev/null +++ b/examples/stm32f7/src/bin/usb_serial.rs @@ -0,0 +1,106 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.hse = Some(mhz(8)); + config.rcc.pll48 = true; + config.rcc.sys_ck = Some(mhz(200)); + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let irq = interrupt::take!(OTG_FS); + let mut ep_out_buffer = [0u8; 256]; + let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index f5673718d..e7273c9fc 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -11,7 +11,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32g071rb", "memory-x", "unstable-pac", "exti"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index ecda28805..8a57a8ef0 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -12,7 +12,7 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [" embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 1a05b9ecb..a04134789 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -8,19 +8,20 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "net", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } -embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16", "unstable-traits"] } -embedded-io = { version = "0.3.0", features = ["async"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } +embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "proto-ipv6"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } -embedded-hal-async = { version = "=0.1.0-alpha.2" } -embedded-nal-async = "0.2.0" +embedded-hal-async = { version = "=0.2.0-alpha.0" } +embedded-nal-async = "0.4.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs index 4ccc0b5ef..cb245c325 100644 --- a/examples/stm32h7/src/bin/eth.rs +++ b/examples/stm32h7/src/bin/eth.rs @@ -7,7 +7,7 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; @@ -22,11 +22,12 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -51,40 +52,33 @@ async fn main(spawner: Spawner) -> ! { let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + singleton!(PacketQueue::<16, 16>::new()), + p.ETH, + eth_int, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI, + mac_addr, + 0, + ); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::Dhcp(Default::default()); + //let config = embassy_net::Config::Static(embassy_net::StaticConfig { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); // Init network stack - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); // Launch network task unwrap!(spawner.spawn(net_task(&stack))); diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 64fd84141..cce85a083 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -7,7 +7,7 @@ use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; use embassy_net::{Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; @@ -23,11 +23,12 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -52,40 +53,33 @@ async fn main(spawner: Spawner) -> ! { let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + singleton!(PacketQueue::<16, 16>::new()), + p.ETH, + eth_int, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI, + mac_addr, + 0, + ); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::Dhcp(Default::default()); + //let config = embassy_net::Config::StaticConfig(embassy_net::Config { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); // Init network stack - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); // Launch network task unwrap!(spawner.spawn(net_task(&stack))); diff --git a/examples/stm32h7/src/bin/i2c.rs b/examples/stm32h7/src/bin/i2c.rs new file mode 100644 index 000000000..d44319ae6 --- /dev/null +++ b/examples/stm32h7/src/bin/i2c.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Error, I2c, TimeoutI2c}; +use embassy_stm32::interrupt; +use embassy_stm32::time::Hertz; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let irq = interrupt::take!(I2C2_EV); + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + irq, + p.DMA1_CH4, + p.DMA1_CH5, + Hertz(100_000), + Default::default(), + ); + + // I2C bus can freeze if SCL line is shorted or due to a broken device that clock stretches for too long. + // TimeoutI2c allows recovering from such errors by throwing `Error::Timeout` after a given delay. + let mut timeout_i2c = TimeoutI2c::new(&mut i2c, Duration::from_millis(1000)); + + let mut data = [0u8; 1]; + + match timeout_i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data) { + Ok(()) => info!("Whoami: {}", data[0]), + Err(Error::Timeout) => error!("Operation timed out"), + Err(e) => error!("I2c Error: {:?}", e), + } +} diff --git a/examples/stm32h7/src/bin/usart.rs b/examples/stm32h7/src/bin/usart.rs index 87c2b1253..405f18ec7 100644 --- a/examples/stm32h7/src/bin/usart.rs +++ b/examples/stm32h7/src/bin/usart.rs @@ -6,6 +6,7 @@ use cortex_m_rt::entry; use defmt::*; use embassy_executor::Executor; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -15,7 +16,8 @@ async fn main_task() { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, NoDma, NoDma, config); + let irq = interrupt::take!(UART7); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, NoDma, NoDma, config); unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); info!("wrote Hello, starting echo"); diff --git a/examples/stm32h7/src/bin/usart_dma.rs b/examples/stm32h7/src/bin/usart_dma.rs index 3adffcbeb..6e3491e55 100644 --- a/examples/stm32h7/src/bin/usart_dma.rs +++ b/examples/stm32h7/src/bin/usart_dma.rs @@ -8,6 +8,7 @@ use cortex_m_rt::entry; use defmt::*; use embassy_executor::Executor; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use heapless::String; use static_cell::StaticCell; @@ -18,7 +19,8 @@ async fn main_task() { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, p.DMA1_CH0, NoDma, config); + let irq = interrupt::take!(UART7); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, p.DMA1_CH0, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32h7/src/bin/usart_split.rs b/examples/stm32h7/src/bin/usart_split.rs index df2b600f8..f97176ecb 100644 --- a/examples/stm32h7/src/bin/usart_split.rs +++ b/examples/stm32h7/src/bin/usart_split.rs @@ -5,6 +5,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::peripherals::{DMA1_CH1, UART7}; use embassy_stm32::usart::{Config, Uart, UartRx}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; @@ -31,7 +32,8 @@ async fn main(spawner: Spawner) -> ! { info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, p.DMA1_CH0, p.DMA1_CH1, config); + let irq = interrupt::take!(UART7); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, irq, p.DMA1_CH0, p.DMA1_CH1, config); unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n")); let (mut tx, rx) = usart.split(); diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs new file mode 100644 index 000000000..9ef520ae2 --- /dev/null +++ b/examples/stm32h7/src/bin/usb_serial.rs @@ -0,0 +1,105 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.sys_ck = Some(mhz(400)); + config.rcc.hclk = Some(mhz(200)); + config.rcc.pll1.q_ck = Some(mhz(100)); + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let irq = interrupt::take!(OTG_FS); + let mut ep_out_buffer = [0u8; 256]; + let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32h7/src/bin/wdg.rs b/examples/stm32h7/src/bin/wdg.rs new file mode 100644 index 000000000..2b0301aad --- /dev/null +++ b/examples/stm32h7/src/bin/wdg.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut wdg = IndependentWatchdog::new(p.IWDG1, 20_000_000); + + unsafe { wdg.unleash() }; + + loop { + Timer::after(Duration::from_secs(1)).await; + unsafe { wdg.pet() }; + } +} diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 7e1120f48..86933a629 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -19,10 +19,10 @@ lorawan-device = { version = "0.8.0", default-features = false, features = ["asy lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" embedded-storage = "0.3.0" -embedded-io = "0.3.0" +embedded-io = "0.4.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32l0/src/bin/usart_dma.rs b/examples/stm32l0/src/bin/usart_dma.rs index 66657d0f0..c307f857a 100644 --- a/examples/stm32l0/src/bin/usart_dma.rs +++ b/examples/stm32l0/src/bin/usart_dma.rs @@ -4,13 +4,15 @@ use defmt::*; use embassy_executor::Spawner; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut usart = Uart::new(p.USART1, p.PB7, p.PB6, p.DMA1_CH2, p.DMA1_CH3, Config::default()); + let irq = interrupt::take!(USART1); + let mut usart = Uart::new(p.USART1, p.PB7, p.PB6, irq, p.DMA1_CH2, p.DMA1_CH3, Config::default()); usart.write(b"Hello Embassy World!\r\n").await.unwrap(); info!("wrote Hello, starting echo"); diff --git a/examples/stm32l0/src/bin/usart_irq.rs b/examples/stm32l0/src/bin/usart_irq.rs index 0e2237388..8e84cd092 100644 --- a/examples/stm32l0/src/bin/usart_irq.rs +++ b/examples/stm32l0/src/bin/usart_irq.rs @@ -4,9 +4,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dma::NoDma; use embassy_stm32::interrupt; -use embassy_stm32::usart::{BufferedUart, Config, State, Uart}; +use embassy_stm32::usart::{BufferedUart, Config, State}; use embedded_io::asynch::{Read, Write}; use {defmt_rtt as _, panic_probe as _}; @@ -21,15 +20,18 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.baudrate = 9600; - let usart = Uart::new(p.USART2, p.PA3, p.PA2, NoDma, NoDma, config); let mut state = State::new(); + let irq = interrupt::take!(USART2); let mut usart = unsafe { BufferedUart::new( &mut state, - usart, - interrupt::take!(USART2), + p.USART2, + p.PA3, + p.PA2, + irq, &mut TX_BUFFER, &mut RX_BUFFER, + config, ) }; diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index 9460febf5..6e3b2103c 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -11,7 +11,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index 657605ebe..5627760ef 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -12,20 +12,18 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } -embedded-hal-async = { version = "=0.1.0-alpha.2" } +embedded-hal-async = { version = "=0.2.0-alpha.0" } panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } micromath = "2.0.0" -usb-device = "0.2" -usbd-serial = "0.1.1" - diff --git a/examples/stm32l4/src/bin/usart.rs b/examples/stm32l4/src/bin/usart.rs index 4a4b46c53..7d874d9d7 100644 --- a/examples/stm32l4/src/bin/usart.rs +++ b/examples/stm32l4/src/bin/usart.rs @@ -4,6 +4,7 @@ use defmt::*; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use {defmt_rtt as _, panic_probe as _}; @@ -14,7 +15,8 @@ fn main() -> ! { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, NoDma, NoDma, config); + let irq = interrupt::take!(UART4); + let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, irq, NoDma, NoDma, config); unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); info!("wrote Hello, starting echo"); diff --git a/examples/stm32l4/src/bin/usart_dma.rs b/examples/stm32l4/src/bin/usart_dma.rs index 728906897..452bede30 100644 --- a/examples/stm32l4/src/bin/usart_dma.rs +++ b/examples/stm32l4/src/bin/usart_dma.rs @@ -7,6 +7,7 @@ use core::fmt::Write; use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; @@ -17,7 +18,8 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, p.DMA1_CH3, NoDma, config); + let irq = interrupt::take!(UART4); + let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, irq, p.DMA1_CH3, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs new file mode 100644 index 000000000..663f60d52 --- /dev/null +++ b/examples/stm32l4/src/bin/usb_serial.rs @@ -0,0 +1,107 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_stm32::rcc::*; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.mux = ClockSrc::PLL(PLLSource::HSI16, PLLClkDiv::Div2, PLLSrcDiv::Div1, PLLMul::Mul10, None); + config.rcc.hsi48 = true; + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let irq = interrupt::take!(OTG_FS); + let mut ep_out_buffer = [0u8; 256]; + let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.max_packet_size_0 = 64; + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 63eac3ed2..c0accb0d6 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -12,12 +12,12 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } usbd-hid = "0.6.0" defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } @@ -26,5 +26,5 @@ embedded-hal = "0.2.6" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } rand_core = { version = "0.6.3", default-features = false } -embedded-io = { version = "0.3.0", features = ["async"] } +embedded-io = { version = "0.4.0", features = ["async"] } static_cell = "1.0" diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index 4f36d3f5a..98ec0e836 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -2,20 +2,16 @@ #![no_main] #![feature(type_alias_impl_trait)] -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; - use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; +use embassy_net::{Stack, StackResources}; use embassy_stm32::rcc::*; use embassy_stm32::rng::Rng; use embassy_stm32::usb::Driver; use embassy_stm32::{interrupt, Config}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; -use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, UsbDevice}; use embedded_io::asynch::Write; use rand_core::RngCore; @@ -28,56 +24,25 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } +const MTU: usize = 1514; + #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { device.run().await } #[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await } #[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { +async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } @@ -106,58 +71,34 @@ async fn main(spawner: Spawner) { config.device_sub_class = 0x02; config.device_protocol = 0x01; - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - // Create embassy-usb DeviceBuilder using the driver and config. let mut builder = Builder::new( driver, config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, - None, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], ); - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - // Our MAC addr. let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); // Build the builder. let usb = builder.build(); unwrap!(spawner.spawn(usb_task(usb))); - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::Dhcp(Default::default()); + //let config = embassy_net::Config::Static(embassy_net::StaticConfig { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), @@ -168,13 +109,7 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - let device = Device { mac_addr: our_mac_addr }; - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); unwrap!(spawner.spawn(net_task(stack))); @@ -221,50 +156,3 @@ async fn main(spawner: Spawner) { } } } - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs index d38ed7496..e3bbe9d09 100644 --- a/examples/stm32l5/src/bin/usb_hid_mouse.rs +++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs @@ -51,7 +51,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs index 7562a4e96..66ccacb73 100644 --- a/examples/stm32l5/src/bin/usb_serial.rs +++ b/examples/stm32l5/src/bin/usb_serial.rs @@ -46,7 +46,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index 3d704011b..2b02eda92 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -9,9 +9,10 @@ embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["de embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" @@ -21,8 +22,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa heapless = { version = "0.7.5", default-features = false } micromath = "2.0.0" - -#[patch.crates-io] -#defmt = { git="https://github.com/knurling-rs/defmt.git" } -#defmt-rtt = { git="https://github.com/knurling-rs/defmt.git" } - diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs new file mode 100644 index 000000000..8cd3bf2f4 --- /dev/null +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -0,0 +1,107 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_stm32::rcc::*; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.mux = ClockSrc::PLL1R(PllSrc::HSI16, PllM::Div2, PllN::Mul10, PllClkDiv::NotDivided); + //config.rcc.mux = ClockSrc::MSI(MSIRange::Range48mhz); + config.rcc.hsi48 = true; + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let irq = interrupt::take!(OTG_FS); + let mut ep_out_buffer = [0u8; 256]; + let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 5b96fa191..e27b4527c 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -11,7 +11,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55cc", "time-driver-any", "exti"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index c827d2b71..690481bbf 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -15,7 +15,7 @@ lorawan-device = { version = "0.8.0", default-features = false, features = ["asy lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32wl/src/bin/random.rs b/examples/stm32wl/src/bin/random.rs new file mode 100644 index 000000000..182c607f9 --- /dev/null +++ b/examples/stm32wl/src/bin/random.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::pac; +use embassy_stm32::rng::Rng; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; + config.rcc.enable_lsi = true; //Needed for RNG to work + + let p = embassy_stm32::init(config); + unsafe { + pac::RCC.ccipr().modify(|w| { + w.set_rngsel(0b01); + }); + } + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); + + loop {} +} diff --git a/examples/stm32wl/src/bin/uart_async.rs b/examples/stm32wl/src/bin/uart_async.rs new file mode 100644 index 000000000..f12fec4c8 --- /dev/null +++ b/examples/stm32wl/src/bin/uart_async.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::interrupt; +use embassy_stm32::usart::{Config, Uart}; +use {defmt_rtt as _, panic_probe as _}; + +/* +Pass Incoming data from LPUART1 to USART1 +Example is written for the LoRa-E5 mini v1.0, +but can be surely changed for your needs. +*/ +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; + let p = embassy_stm32::init(config); + + defmt::info!("Starting system"); + + let mut config1 = Config::default(); + config1.baudrate = 9600; + + let mut config2 = Config::default(); + config2.baudrate = 9600; + + //RX/TX connected to USB/UART Bridge on LoRa-E5 mini v1.0 + let irq = interrupt::take!(USART1); + let mut usart1 = Uart::new(p.USART1, p.PB7, p.PB6, irq, p.DMA1_CH3, p.DMA1_CH4, config1); + + //RX1/TX1 (LPUART) on LoRa-E5 mini v1.0 + let irq = interrupt::take!(LPUART1); + let mut usart2 = Uart::new(p.LPUART1, p.PC0, p.PC1, irq, p.DMA1_CH5, p.DMA1_CH6, config2); + + unwrap!(usart1.write(b"Hello Embassy World!\r\n").await); + unwrap!(usart2.write(b"Hello Embassy World!\r\n").await); + + let mut buf = [0u8; 300]; + loop { + let result = usart2.read_until_idle(&mut buf).await; + match result { + Ok(size) => { + match usart1.write(&buf[0..size]).await { + Ok(()) => { + //Write suc. + } + Err(..) => { + //Wasnt able to write + } + } + } + Err(_err) => { + //Ignore eg. framing errors + } + } + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 1ec19e58b..da75fa53a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,8 +1,8 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-09-22" -components = [ "rust-src", "rustfmt" ] +channel = "nightly-2023-02-07" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] targets = [ "thumbv7em-none-eabi", "thumbv7m-none-eabi", diff --git a/stm32-data b/stm32-data index 14a448c31..662529829 160000 --- a/stm32-data +++ b/stm32-data @@ -1 +1 @@ -Subproject commit 14a448c318192fe9da1c95a4de1beb4ec4892f1c +Subproject commit 66252982939014e94fc4a1b7423c30c3d108ae0b diff --git a/stm32-metapac-gen/Cargo.toml b/stm32-metapac-gen/Cargo.toml index 3c1dab57a..6d136ba6b 100644 --- a/stm32-metapac-gen/Cargo.toml +++ b/stm32-metapac-gen/Cargo.toml @@ -7,7 +7,9 @@ license = "MIT OR Apache-2.0" [dependencies] regex = "1.5.4" -chiptool = { git = "https://github.com/embassy-rs/chiptool", rev = "28ffa8a19d84914089547f52900ffb5877a5dc23" } +chiptool = { git = "https://github.com/embassy-rs/chiptool", rev = "1d9e0a39a6acc291e50cabc4ed617a87f06d5e89" } serde = { version = "1.0.130", features = [ "derive" ] } +serde_json = "1.0.87" serde_yaml = "0.8.21" proc-macro2 = "1.0.29" + diff --git a/stm32-metapac-gen/src/lib.rs b/stm32-metapac-gen/src/lib.rs index 9bd60cb79..64045986e 100644 --- a/stm32-metapac-gen/src/lib.rs +++ b/stm32-metapac-gen/src/lib.rs @@ -223,7 +223,7 @@ impl Gen { fn load_chip(&mut self, name: &str) -> Chip { let chip_path = self.opts.data_dir.join("chips").join(&format!("{}.json", name)); let chip = fs::read(chip_path).expect(&format!("Could not load chip {}", name)); - serde_yaml::from_slice(&chip).unwrap() + serde_json::from_slice(&chip).unwrap() } pub fn gen(&mut self) { diff --git a/stm32-metapac/Cargo.toml b/stm32-metapac/Cargo.toml index 9d5aba0c0..2605cf3d3 100644 --- a/stm32-metapac/Cargo.toml +++ b/stm32-metapac/Cargo.toml @@ -27,6 +27,7 @@ flavors = [ { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, @@ -67,6 +68,19 @@ memory-x = [] # BEGIN GENERATED FEATURES # Generated by stm32-gen-features. DO NOT EDIT. +stm32c011d6 = [] +stm32c011f4 = [] +stm32c011f6 = [] +stm32c011j4 = [] +stm32c011j6 = [] +stm32c031c4 = [] +stm32c031c6 = [] +stm32c031f4 = [] +stm32c031f6 = [] +stm32c031g4 = [] +stm32c031g6 = [] +stm32c031k4 = [] +stm32c031k6 = [] stm32f030c6 = [] stm32f030c8 = [] stm32f030cc = [] @@ -1275,11 +1289,9 @@ stm32u575zi = [] stm32u585ai = [] stm32u585ci = [] stm32u585oi = [] -stm32u585qe = [] stm32u585qi = [] stm32u585ri = [] stm32u585vi = [] -stm32u585ze = [] stm32u585zi = [] stm32wb10cc = [] stm32wb15cc = [] @@ -1297,7 +1309,6 @@ stm32wb55vc = [] stm32wb55ve = [] stm32wb55vg = [] stm32wb55vy = [] -stm32wb5mmg = [] stm32wl54cc-cm4 = [] stm32wl54cc-cm0p = [] stm32wl54jc-cm4 = [] @@ -1306,8 +1317,6 @@ stm32wl55cc-cm4 = [] stm32wl55cc-cm0p = [] stm32wl55jc-cm4 = [] stm32wl55jc-cm0p = [] -stm32wl55uc-cm4 = [] -stm32wl55uc-cm0p = [] stm32wle4c8 = [] stm32wle4cb = [] stm32wle4cc = [] @@ -1320,6 +1329,4 @@ stm32wle5cc = [] stm32wle5j8 = [] stm32wle5jb = [] stm32wle5jc = [] -stm32wle5u8 = [] -stm32wle5ub = [] # END GENERATED FEATURES diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index d6770d6e9..572a9ce88 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -8,20 +8,21 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] } -embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver"] } +embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver", "critical-section-impl"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3.0" -defmt-rtt = "0.3.0" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6" } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } -embedded-hal-async = { version = "=0.1.0-alpha.2" } +embedded-hal-async = { version = "=0.2.0-alpha.0" } panic-probe = { version = "0.3.0", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } -embedded-io = { version = "0.3.0", features = ["async"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embedded-storage = { version = "0.3" } [profile.dev] debug = 2 diff --git a/tests/rp/src/bin/flash.rs b/tests/rp/src/bin/flash.rs new file mode 100644 index 000000000..897e3804f --- /dev/null +++ b/tests/rp/src/bin/flash.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::flash::{ERASE_SIZE, FLASH_BASE}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDR_OFFSET: u32 = 0x4000; + +#[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(Duration::from_millis(10)).await; + + let mut flash = embassy_rp::flash::Flash::<_, { 2 * 1024 * 1024 }>::new(p.FLASH); + + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.erase(ADDR_OFFSET, ADDR_OFFSET + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.read(ADDR_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.write(ADDR_OFFSET, &mut buf)); + + defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + info!("Contents after write starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xDA) { + defmt::panic!("unexpected"); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/gpio.rs b/tests/rp/src/bin/gpio.rs index af22fe27d..80e92d0fd 100644 --- a/tests/rp/src/bin/gpio.rs +++ b/tests/rp/src/bin/gpio.rs @@ -78,6 +78,7 @@ async fn main(_spawner: Spawner) { a.set_as_input(); // When an OutputOpenDrain is high, it doesn't drive the pin. + b.set_high(); a.set_pull(Pull::Up); delay(); assert!(a.is_high()); @@ -85,9 +86,8 @@ async fn main(_spawner: Spawner) { delay(); assert!(a.is_low()); - b.set_low(); - // When an OutputOpenDrain is low, it drives the pin low. + b.set_low(); a.set_pull(Pull::Up); delay(); assert!(a.is_low()); @@ -95,14 +95,36 @@ async fn main(_spawner: Spawner) { delay(); assert!(a.is_low()); + // Check high again b.set_high(); - a.set_pull(Pull::Up); delay(); assert!(a.is_high()); a.set_pull(Pull::Down); delay(); assert!(a.is_low()); + + // When an OutputOpenDrain is high, it reads the input value in the pin. + b.set_high(); + a.set_as_input(); + a.set_pull(Pull::Up); + delay(); + assert!(b.is_high()); + a.set_as_output(); + a.set_low(); + delay(); + assert!(b.is_low()); + + // When an OutputOpenDrain is low, it always reads low. + b.set_low(); + a.set_as_input(); + a.set_pull(Pull::Up); + delay(); + assert!(b.is_low()); + a.set_as_output(); + a.set_low(); + delay(); + assert!(b.is_low()); } // FLEX diff --git a/tests/rp/src/bin/multicore.rs b/tests/rp/src/bin/multicore.rs new file mode 100644 index 000000000..da78e887a --- /dev/null +++ b/tests/rp/src/bin/multicore.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_executor::_export::StaticCell; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<1024> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL0: Channel = Channel::new(); +static CHANNEL1: Channel = Channel::new(); + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task()))); + }); + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("CORE0 is running"); + let ping = true; + CHANNEL0.send(ping).await; + let pong = CHANNEL1.recv().await; + assert_eq!(ping, pong); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +#[embassy_executor::task] +async fn core1_task() { + info!("CORE1 is running"); + let ping = CHANNEL0.recv().await; + CHANNEL1.send(ping).await; +} diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs index 9cc20bb98..bea9283e7 100644 --- a/tests/rp/src/bin/uart_buffered.rs +++ b/tests/rp/src/bin/uart_buffered.rs @@ -5,7 +5,7 @@ use defmt::{assert_eq, *}; use embassy_executor::Spawner; use embassy_rp::interrupt; -use embassy_rp::uart::{BufferedUart, Config, State, Uart}; +use embassy_rp::uart::{BufferedUart, Config}; use embedded_io::asynch::{Read, Write}; use {defmt_rtt as _, panic_probe as _}; @@ -17,25 +17,22 @@ async fn main(_spawner: Spawner) { let (tx, rx, uart) = (p.PIN_0, p.PIN_1, p.UART0); let config = Config::default(); - let uart = Uart::new_blocking(uart, tx, rx, config); - let irq = interrupt::take!(UART0_IRQ); let tx_buf = &mut [0u8; 16]; let rx_buf = &mut [0u8; 16]; - let mut state = State::new(); - let mut uart = BufferedUart::new(&mut state, uart, irq, tx_buf, rx_buf); + let mut uart = BufferedUart::new(uart, irq, tx, rx, tx_buf, rx_buf, config); // Make sure we send more bytes than fits in the FIFO, to test the actual // bufferedUart. let data = [ - 1_u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, + 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, ]; uart.write_all(&data).await.unwrap(); info!("Done writing"); - let mut buf = [0; 32]; + let mut buf = [0; 31]; uart.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, data); diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index bebbf557e..08a775eae 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -20,13 +20,13 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-tim2"] } defmt = "0.3.0" -defmt-rtt = "0.3.0" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } -embedded-hal-async = { version = "=0.1.0-alpha.2" } +embedded-hal-async = { version = "=0.2.0-alpha.0" } panic-probe = { version = "0.3.0", features = ["print-defmt"] } [profile.dev] diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs index 7673bfe6d..af55867f2 100644 --- a/tests/stm32/src/bin/usart.rs +++ b/tests/stm32/src/bin/usart.rs @@ -7,6 +7,7 @@ mod example_common; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use example_common::*; @@ -18,22 +19,22 @@ async fn main(_spawner: Spawner) { // Arduino pins D0 and D1 // They're connected together with a 1K resistor. #[cfg(feature = "stm32f103c8")] - let (tx, rx, usart) = (p.PA9, p.PA10, p.USART1); + let (tx, rx, usart, irq) = (p.PA9, p.PA10, p.USART1, interrupt::take!(USART1)); #[cfg(feature = "stm32g491re")] - let (tx, rx, usart) = (p.PC4, p.PC5, p.USART1); + let (tx, rx, usart, irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1)); #[cfg(feature = "stm32g071rb")] - let (tx, rx, usart) = (p.PC4, p.PC5, p.USART1); + let (tx, rx, usart, irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1)); #[cfg(feature = "stm32f429zi")] - let (tx, rx, usart) = (p.PG14, p.PG9, p.USART6); + let (tx, rx, usart, irq) = (p.PG14, p.PG9, p.USART6, interrupt::take!(USART6)); #[cfg(feature = "stm32wb55rg")] - let (tx, rx, usart) = (p.PA2, p.PA3, p.LPUART1); + let (tx, rx, usart, irq) = (p.PA2, p.PA3, p.LPUART1, interrupt::take!(LPUART1)); #[cfg(feature = "stm32h755zi")] - let (tx, rx, usart) = (p.PB6, p.PB7, p.USART1); + let (tx, rx, usart, irq) = (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1)); #[cfg(feature = "stm32u585ai")] - let (tx, rx, usart) = (p.PD8, p.PD9, p.USART3); + let (tx, rx, usart, irq) = (p.PD8, p.PD9, p.USART3, interrupt::take!(USART3)); let config = Config::default(); - let mut usart = Uart::new(usart, rx, tx, NoDma, NoDma, config); + let mut usart = Uart::new(usart, rx, tx, irq, NoDma, NoDma, config); // We can't send too many bytes, they have to fit in the FIFO. // This is because we aren't sending+receiving at the same time. diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs index e0389446f..d12605a9a 100644 --- a/tests/stm32/src/bin/usart_dma.rs +++ b/tests/stm32/src/bin/usart_dma.rs @@ -6,6 +6,7 @@ mod example_common; use defmt::assert_eq; use embassy_executor::Spawner; +use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, Uart}; use example_common::*; @@ -17,22 +18,53 @@ async fn main(_spawner: Spawner) { // Arduino pins D0 and D1 // They're connected together with a 1K resistor. #[cfg(feature = "stm32f103c8")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PA9, p.PA10, p.USART1, p.DMA1_CH4, p.DMA1_CH5); + let (tx, rx, usart, irq, tx_dma, rx_dma) = ( + p.PA9, + p.PA10, + p.USART1, + interrupt::take!(USART1), + p.DMA1_CH4, + p.DMA1_CH5, + ); #[cfg(feature = "stm32g491re")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2); + let (tx, rx, usart, irq, tx_dma, rx_dma) = + (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32g071rb")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2); + let (tx, rx, usart, irq, tx_dma, rx_dma) = + (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32f429zi")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PG14, p.PG9, p.USART6, p.DMA2_CH6, p.DMA2_CH1); + let (tx, rx, usart, irq, tx_dma, rx_dma) = ( + p.PG14, + p.PG9, + p.USART6, + interrupt::take!(USART6), + p.DMA2_CH6, + p.DMA2_CH1, + ); #[cfg(feature = "stm32wb55rg")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PA2, p.PA3, p.LPUART1, p.DMA1_CH1, p.DMA1_CH2); + let (tx, rx, usart, irq, tx_dma, rx_dma) = ( + p.PA2, + p.PA3, + p.LPUART1, + interrupt::take!(LPUART1), + p.DMA1_CH1, + p.DMA1_CH2, + ); #[cfg(feature = "stm32h755zi")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, p.DMA1_CH0, p.DMA1_CH1); + let (tx, rx, usart, irq, tx_dma, rx_dma) = + (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH0, p.DMA1_CH1); #[cfg(feature = "stm32u585ai")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PD8, p.PD9, p.USART3, p.GPDMA1_CH0, p.GPDMA1_CH1); + let (tx, rx, usart, irq, tx_dma, rx_dma) = ( + p.PD8, + p.PD9, + p.USART3, + interrupt::take!(USART3), + p.GPDMA1_CH0, + p.GPDMA1_CH1, + ); let config = Config::default(); - let mut usart = Uart::new(usart, rx, tx, tx_dma, rx_dma, config); + let mut usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config); // We can't send too many bytes, they have to fit in the FIFO. // This is because we aren't sending+receiving at the same time.